Mbed上のARMマイコンでDSP遊びを②! 【2021.10.12】




 さて、これ以降は三上さんのTR技術の記事を参考に、いろいろ処理ブロックの「動き」を見て、特性を実感しながら最終的にはダイレクトサンプリングの7MHz SSB用のSDRに仕上げてみたいと思います。これまで、いろいろSDRを構成する基本的なコンポーネントに関して、自らプログラムしてその実現特性を確認する作業を重ねて来ました。DSPはアナログと異なり、「処理の中身」を正しく理解していないと、プログラムしても「うんともすんとも」動いてくれません。アナログだと、不適切な部分があってもなんとなく動くことも多く、特性を見ながらこの是正ができますが、DSPの場合は動くか動かないか、まさに0 or 1の世界なので、基本のコンポーネントというか数式処理の中身を理解しないと、先に進めなくなります。

 なので、時間は掛かりますが、先達のバイナリーファイルを実行するだけの体験から、少しでも自分の頭で考えて物を作る体験に移行できればと思っています。できるものは拙くても、先達のスマートなコードをそのまま実行させる「だけの行動」よりはスキルが上がるのでは?と勝手に思っています。

 さて、生意気な能書きはこれくらいにして、どう進めましょうか? はい、いつもDSPでは参考にさせていただいている、三上さんがトラ技21年1月号~5月号に「Armマイコンで作るダイレクトサンプリングSDR」を連載されていました。この記事はNucleo F466REのSTM32基板で(FPGAを用いることなく)ダイレクトサンプリングを実現していて、その構成要素の理解はDSP理解のためには大変に勉強になりますので、これに沿って実験をしてみることにします。

 最初は、Tr技に書いてある通り実験をしていたのですが、A/Dの特性を見ているうちに、HFローバンドでもアンダーサンプルならば実行できそうなことに気づき、以降はSSB/AMの受信までたどり着くことができました。プログラムの骨格は、ほぼ三上さんのコードをコピーしていますが、例のごとく、クラスできれいに書かずに、Cライクながさつなコードに変えてしまっています。ま、同じことができるなら、私の興味はあくまでも「実現特性」なので、あまり、コードの奇麗さにはこだわらないことにします。

 ということで、まずは三上記事のオリジナルのBC帯AM受信機をいじくりまわすことにします。

 最初に、簡単に三上記事から引用しておきます。





処理の概要(三上著TR技術2021年3月号p139-140より)


 処理は3段階に分かれていて、優先順に第一段(AdcIsr())、第二段(IsrCIC())、第三段(IsrFIR())という割り込みサービスルーチンの中に各処理を行います。上図左の処理で第一段のAdcIsr()から右の処理IsrCIC()に移って行きます。要するに、入力信号をA/D変換してNCOでゼロIFに落とし、2段に分けた帯域制限とダウンサンプル(デシメーション)を行って最後に復調処理をしています。このプログラムは、本機の特徴である、ダイレクトサンプリングをアンダーサンプルとすることで、特性の限られたA/DをうまくRFで用いる手法を勉強することができます。たしか、SVZ林様も、TR技術やデザインウェーブ(廃刊)にディジタルFMチューナーの記事を書かれていたかと思いますが、これもアンダーサンプリングだったと記憶しています。





アンダーサンプルの様子

今回のアンダーサンプリング周波数(900kHz)の場合、NCOでダウンサンプルされると7MHz帯を受信するとSSBはサイドバンド反転が起きる
BC帯はサイドバンド反転の起きる領域(450~900kHz)と起きない領域(900~1800kHz)になるが、AMなので問題はない



 さて、三上記事では、このアンダーサンプルのサンプリングレートを900kspsに決めています。これが、記事によるとBC帯の放送波がDSP処理によって「混信」しないためのレートから決められたようです。今回は、まずは三上記事のフォローを行い、次の段階で、受信周波数をどこまで拡張できるかトライしてみることにします。なので、なにはともあれBC帯のTOP BPFが必要になります。三上記事に掲載されているパッシブフィルタを完全コピーして、RFアンプと一緒に組んだのが以下の図です。





BC帯域BPF外観

右側がNJU77701OPA




BC帯BPF回路図(三上著TR技術2021年1月号p116より)




BC帯BPF特性


 これを用いて、Nucleo F446のA/DポートであるA1入力に直結します。まさにダイレクトサンプリングというわけです。RF信号をそのままA/Dするという、高級SDRと同じ「機能」をこの小さなボードで実現できるのですから、大変に面白いですね。もちろん、F特性、処理語長とかの「性能」はまったく及ぶところではないのですが。

 そうは言ってもこれで受信できてしまうのですから、不思議なものです。QSDとかアナログミクサが要らないのは、根っからのアナログ人間としてはちょっと異質に感じてしまいます(苦笑)。で、とりあえず、受信してみたのが以下ですが、夕方の時間でなんだか雑音が多く、あまりクリアな音質ではありません。これについては、これ以降改善の検討をしていきます。





三上ラジオの受信風景


 この状態から、AM「通信」用に通過帯域を狭めることにトライしてみます。三上SDRの構成は、


A/D入力 → NCOと乗算してゼロIF → Fs=900Kspsでアンダーサンプル

CICデシメータでFSを1/9の100ksps → 主体域制限FIR→デシメータでFsを1/4の25ksps


 という処理をしていますので、CICデシメータ、後段のデシメータと主体域制限のFIRの特性をいじくりまわすことにします。まずはオリジナルな特性を実感してみましょう。





CICフィルタの振幅特性実測




CIC+FIRフィルタの振幅特性実測



 最初の図でCICフィルタの特性を取ってみます。これは、Nucleo F446REのトップに受信周波数から離調したキャリアを入力して、CICフィルタ通過後のポイントでD/Aしてレベルを見たものです。また2番目の図ではこれにさらにFIRフィルタを通したあとでの3段目の割り込みサービスルーチンIsrFIR()内でD/Aして、同じくレベルを見たものです。

 このように、設計された特性がSDRの内部で動作を見ることができるのは、実際のSDR動作が実感できて極めて有用ではないかと思います。このCICフィルタのデシメーション数やFIRフィルタの特性を変更して、特性を最適化することができます。ここでは、AM「通信」用に、帯域を狭くすることを考えてみます。まずはCICフィルタのデシメーション数をオリジナルの9から20、45と増加させてみます。





CICデシメータ=20の場合の振幅減衰特性




CICデシメータ=45の場合の振幅減衰特性


 このように、デシメーション数を増やしていくにつれて、当たり前ですが帯域が狭くなっていきます。このまま、CICフィルタだけで、狭帯域化できるかとも思いますが、周波数が離れたところの減衰量は必ずしも多く取れませんので、やっぱりFIRを併用することになりそうです。いろいろトライしたところ、AM通信用らしき特性を得るのに、CICデシメーション数N=30、FIRは50次で設計してみたのが以下の図です。





CICデシメータ=20 50次FIRでBWを3kHzに設定


 このフィルタを実装して、Nucleoのトップからキャリアを入力して、FIRフィルタ通過後の点でレベルを見たのが赤点です。まぁまぁ設計通りの値になっています。I/Qチャネルのいずれもが同じ特性のフィルタを通過させています。もちろん、初段のCICフィルタの特性も込々の特性です。3kHzまで通過させれば、通信用としては十分だろうとの思惑です。ここまで来たので、実際に受信してみることにしましょう。7MHzのAM通信はたまにしか受信できないので、NCO周波数を下げてBC帯の放送波を受信したのが下記の動画の様子です。





帯域を3kHzに狭めた場合の様子


 先の、受信帯域が10kHz程度の放送波用フィルタと異なり、高域がそぎ落とされているので、「通信の音」になっているのが解ります。ということで、ここで、いったん区切って、次回はSSBの受信にチャレンジします。SSBが受信できればFT8も受信できるはずなので、ご期待ください。



<Under Sampling 7MHz Receiver> 【2021.10.31】

 さて、そろそろNucleo F446REを使ったSDRの最終形が近くなってきました。前回に説明したUnder Samplingの原理に従ったDirect Sampling Receiverについて書いておきます。事の発端は三上記事をトレースしているうちに、昔の知識を思い出したからです。それは、A/D変換器の特性が規格のサンプリング周波数を超えて伸びていれば、Under Samplingと言って、受信する周波数よりも低いサンプリング周波数でサンプリングしても、信号が復元できるというものです。実際、この原理を用いてFsが150MspsくらいのA/Dで1GHzとかの信号をUnder Sampleしている例もあるようです。この用途のA/D変換器は、Under Sampling用にアナログF特性はかなり延ばしてあるようです。

 このように、A/D変換器でUnder Sampleするというのは一種の周波数変換にも似た動作になりますね。但し、先の図に示したように、サンプリング周波数の帯域毎に「盛大に」イメージ受信が発生しますから、トップのアナログBPFが極めて重要な構成要素になることは言うまでもありません。

 ということなので、まずは、STM32F466チップに内臓のA/D変換器がどれほどの能力を持っているのか、調べる必要があります。これが、全ての始まりになります。その方法ですが、まずは、SSGの信号をA/Dして、チップ内部のNCOのローカル周波数と混合することで、ベースバンド信号に変換します。この時、受信周波数(=SSG信号周波数)とNCOの周波数差が例えば1kHzにとると、この「ベースバンド変換された信号レベル」の受信周波数依存性をプロットしてみれば、概略の特性を見て取ることができます。





BC帯Topフィルタを誤って挿入した場合のA/D変換特性


 キャプションにもある通り笑ってしまうのですが、A/Dの素の特性を見ようと思って、三上記事にあるBC帯の1MHzを基準に、入力周波数を変えていって、ベースバンドに落ちる1kHz(すなわち受信周波数とNCOの差が1kHz)のレベルを見たのが上図です。最初、データを取っていて、うーーん、こんなものなのかな?3.5MHzあたりで、ギリ使えるかなぁ????7MHzは無理だな、なんて思っていました。どうせチップ内臓A/Dなのだし、多くは望めないだろうと諦め掛けました。ところが、ふと見ると、トップにBC帯のBPFがついていることに気が付いて、愕然としました。大ポカです。





A/D変換器の素のF特性


 A/Dの素の特性を見てみると、-3dB点はなんと13MHzにもなって、3.5、7,10MHz帯ではこのA/DでUnder Samplingができる可能性が示されました。途中、4MHz、9MHzではDCに近い周波数域に不要な線スペクトラムが立って、SSB/AMの復調には向かないだろうなぁ・・という状態だったので、除いてあります。もしかすると、ピンポイントではなく舐めてサーチするとそういう周波数もあるかもしれませんが、ここでは深堀りはしません。いずれにしても、この図でA/D変換器のUnder Sampling能力が解ったのでとりあえず、7MHzを中心にSDRにむけて処理を詰めてみたいと思います。




ベースバンドスペクトラム特性




ベースバンドスペクトラム特性


 上図はベースバンドに落ちた7101kHzのA/D入力信号のスペクトラムです。NCOと入力信号を乗算した信号をD/Aしてスペクトラムを見たものです。これは、Under Samplingという、「内部妨害の出やすそうな方式」でサンプリングしているので、なにやら変なスプリアスがあるかどうか気になったからです。通信に用いるDC~3kHzくらいの領域では特段目だったスプリアスがなさそうで、50kHz帯域でも、38kHz離れくらいに小さなスプリアスが見える程度ですので、まったく問題なくフィルタリングできることを確認しました。ここまでで、基本的な特性確認はできました。SDRとして動作させることができそうです。


 ここでふと考え込んだのは、オリジナルな3段構成の割り込みによる2段階のダウンサンプルと2回路のフィルタリングという構成を、2段階の割り込みで1段階のダウンサンプルと2回路のフィルタリングで、SSB/AM信号を受信することができないか・・・ということです。オリジナルでは

  900ksps→100ksps→25ksps

 とサンプリング周波数を変化させていますが、この構成をそのまま採用すると特にSSBの帯域制限にCPUタイムが追いつかないことになってしまって、これ以上進めなくなります。なぜかというと100kspsでFIRの処理を行っていますが、このFsで帯域が2kHzそこそこのSSB帯域フィルタを作るとすると、次数がとんでもないことになるからです。興味のある方は、三上さんのフィルタシミュレーションモデルで調べてみてください。私は、はなから、今のマイコンでは、100kspsじゃむっりーと思ったので、即方針転換しました(笑)。

 そこで考えたのが、初回のダウンサンプル数を大きく取って、SSB帯域制限の比較的楽なFsを採用しようということです。すなわち、

 900ksps→15~25ksps

 とCICフィルタで大きくダウンサンプルして、そこで、FIRで主帯域制限を課そうと考えました。この構成だとオリジナルの構成よりCICフィルタのディップ点の周波数幅が狭まるので、FIRにおけるFsの折り返し抑圧の減衰量が若干心配ですが、背に腹は替えられません。できるところまでやってみます。

 で、まずは直感的に、ダウンサンプルレートを1/45、Fs=20kspsで、SSB用の複素フィルタを作ってみます。このあたりは、Fs、通過帯域、次数が絡み合うので、Cut & Tryで進めることにします。第一段で作ったのが以下のフィルタ設計例です。





Fs=20kspsによるSSB用複素フィルタの素(ベースとなるFIRフィルタ)


 パラメータとしてはFs=20ksps、68次、減衰域は0.85kHzから1.82kHz、Shape Factor=1.34 -6dB点=1.35kHz、ー60dB点=1.82kHz、 BW=2.46kHz(複素BPF時) てな物です。細かい周波数の刻みは、いろいろ弄った結果(笑)です。このFIRを複素BPFに変換して、SSB受信機として動作させてみて復調させたのが、以下の図になります。





とりあえず復調させたの図

 周波数が7100kHzのキャリアをNucleo F446REのA/D(A1)入力に直接入力し、復調したベースバンドのスペクトラムです。ここでも、Fs折り返しの19kHzが生成されてしまいますが、D/Aがオーバーサンプル機能を持つオーディオCODECではないので、仕方ありません。当たり前ですが、三上オリジナルでも盛大に出力されています。ただ、一応、目論見通り復調動作ができていることは確認することができました。

 さて、ここまでくると、受信機としてのRF特性を整えることを考えないといけません。まずは、感度と近接妨害の除去についてです。ここに示したように、Uner Sampling方式の受信機はサンプリング周波数帯域毎にイメージ受信を繰り返すことを思い出してください。こいつを何とか低減するためにうってつけのフィルタ(BPF)があることを思い出しました。そうです、セラロックを使ったBPFでけっこうまともな特性を得ることができます。ここに書いてある、高い周波数の通り抜け?には目をつぶって(この帯域はA/Dが反応しない!)、採用してしまうことにします。





セラロックを使った7MHz帯BPF




通過帯域特性




通過帯域特性


 あとは、感度の補正が必要になります。もともとCPU内臓のチンケな12ビットのA/D変換器を使ってダイレクトにRF信号を取り込んでいるので、当然のことながらダイナミックレンジが狭く、入力信号をA/Dの扱えるレンジにまで増幅しなければなりません。最初は下記に示すごとく2SK241の単段アンプではゲイン不足で所定の感度を得ることができなかったので、ちょっと不細工ですが、ゲイン可変のアンプであるAD603を後置して、利得を最小限にしてセットアップしてみました。





トップアンプの構成(暫定です)



 当時のツイッターによると、AD603の追加ゲインは10dB程度でトータルな感度として1μVrms@50Ω程度になっているようです。7MHzではこの辺りで十分で、ハンドリング電圧範囲の狭い(ダイナミックレンジの狭い)今回のようなSDRではいたずらに感度を上げるのは禁物です。今回は暫定的にこの程度にしてあります。

 実は、ダイレクトサンプルにして、かなり特性が良好になったポイントがあります。それは、逆サイドバンドの抑圧比です。通常、簡易なSDRとしては、トップにQSDなる直交サンプリング(?)ミクサを用いて、I/Qローカルと乗算して、ベースバンドI/Q信号を作りますが、この方式だと、補正を行わないと、バランス取りをしっかりやっても、-50dB程度の逆サイド抑圧比になってしまいます。これだと、混雑していて、大信号局の多い7MHzだと、逆サイドのサイド混信でざわつく感じがすることがあります。





逆サイドイメージサプレッションの様子


 具体的にはローカルが7100kHzで、LSBの7099kHzを受信しておき、この復調レベルが-10dBmになる信号の周波数を7101kHzにシフトさせて、LSB受信される1kHzの信号レベルを見たものです。これが-82.7dBmとなったので、逆サイドバンド抑圧比は72.7dBということになります。これはうまく作ったアナログ6素子のXFと同程度、下手をすると8素子でもこれくらいしか取れないこともありますので、上々の特性と考えることができます。おまけに単なる演算なので、特性の経年変化もありませんのでグッドですね。

 ここまで来ると実際に聞いてみたくなるのが人情ですが、その前に主体域制限の複素BPFの特性を微調整しておきます。先に提示したフィルタだと、LPF高域の減衰特性がなだらかになってしまいます。これを1.82kHzシフトして、1.82kHzから下の領域は中心を1.82kHzにとって対称のHPFとなりますので、トータルとしてのBPFの低域特性が犠牲になって、低音が通りません。なので、最終的にはFsを15kHzに下げて(当然CICデシメータは1/60になる)、さらにはベースLPFの次数を(複素BPFの次数も同じ)90に上げて、もう少し高域の切れが良くて、低域の減衰の少ないBPFを作ってみました。シフト周波数は1.5kHzで、通過帯域は2.4kHz程度になり、ちょうどいい感じです。実は、こんな微調整を行っているのは、CPUの能力と実現特性を天秤にかけて、納得できそうな領域を探すトライをしているのです。CPU処理能力が高ければ、剛腕でエイヤとやれるのでしょうが、こういうチマチマした「最適化」もやってみると以外と楽しいものです(笑)。





SSBに最適化した複素BPF生成用ベースLPFの特性


 これで、だいぶ以前のフィルタよりも聞きやすくなりました。最後に実際の復調の様子を動画に撮っておきます。





復調の様子


 実は、まだこの時点では、周波数チューニングはSDR単独では行えず、PCからターミナルソフトでシリアル的に与える形になっています。ま、とりあえず受信はできますが、やはり、ロータリーエンコーダとOLEDで表示をさせたくなります。これについては、時間のあるときに遊んでみたいと思います。

 いままで、拙い営みですが、実際にSDRの各機能ブロックを、その機能単体で特性測定することによって、動作の理解を深めてきました。まだまだ、AGCや先の周波数選局、さらには、アップサンプルによるFs折り返し成分の低減等々、やってみたいことが目白押しなので、少しずつ実行してみたいと思っています。

 今回の最後に、インクルードファイルを除いた記述を晒しておきますので(但しタブがとんでしまって見ずらいのはいつものごとくです)、興味のある方は三上オリジナルコードと比較していただければと思います。ソフトウェアのドシロートが書いていますので、いろいろ変なところががあるでしょうが、そこはご容赦を。

//-------------------------------------------------------------
// 7MHz HAM 用 SDR,CIC フィルタと通常の FIR フィルタ使用
//
// 1/60にダウンサンプリング:CIC フィルタ使用
// 1/1  Fs=15kHz FIR フィルタ使用
//
// 入力ピン: A1
// 出力ピン: A2
//
// CIC Decimater:1/60 Fs=900k、15k
// Complex Filter :Order=90
// Frequency controll by USB Kyboard
//
//-------------------------------------------------------------

#include "F446_ADC_Intr.hpp" // ADC
#include "F446_DAC.hpp" // DAC
#include "QuadOscIir.hpp" // 直交信号発生器
#include "Cic3Stage.hpp" // 3段 CIC フィルタ
#include "CoefficientsLpf.hpp" // FIR フィルタの係数
#include "IirCascadeCoefficients.hpp"//Weaver用LPFを収容
#include "FirFastSymmetry.hpp" // 係数対称 FIR フィルタ
#include "FirCoefficients.hpp" //複素フィルタ90次
#include "Iir1st.hpp" // 一次の IIR フィルタ
#include "InitialMessage.hpp" // 最初のメーッセージ
using namespace Mikami;
#pragma diag_suppress 870 // マルチバイト文字使用の警告抑制のため

const float FS_ = 900.0f; // 標本化周波数,単位: kHz
const float T0_ = 1000/FS_; // 標本化間隔,単位:μs
const float A0_ = 8192; // 直交信号発生器の出力の振幅

// 各放送局の搬送波の周波数,単位: Hz
const float F_C_[] = { 7100.0e3f, // 7MHz Hamband
7100.1e3f, // NHK 第2
810.0e3f, // AFN Tokyo
954.0e3f, // TBS ラジオ0
1134.0e3f, // 文化放送
1242.0e3f, // ニッポン放送
3550.0e3f}; // ラジオ日本

float Freq_R=7100.0e3f; //周波数可変時初期周波数

AdcF446_Intr adc_(FS_, A1); // ADC オブジェクトの宣言
DacF446 dac_; // DAC オブジェクトの宣言
Serial pc_(USBTX, USBRX); // ターミナルに対する受信割込み用

QuadOscIir nco_(F_C_[0], T0_, A0_); // NCO (numerically controlled oscillator)

struct Delaylp { float vn1lp, vn2lp; }; // IIR構造体(LPF)
Delaylp vk_lp[N_SECTS_lp]; // 遅延器(LPF)
struct Delaylp2 { float vn1lp2, vn2lp2; }; // IIR構造体2(LPF)
Delaylp2 vk_lp2[N_SECTS_lp2]; // 遅延器(LPF)
//FIR関係定義-
float xk_[ORDER_fir+1]; // Real 側 LPF
float Buff_[2*ORDER_fir+1]; // First Fir用Buffer
int length = ORDER_fir; // Fir length = ORDER_
unsigned index;
//FIR関係定義-
float xk_sp[ORDER_fir_sp+1]; // Image側 LPF
float Buff_sp[2*ORDER_fir_sp+1]; // First Fir用Buffer
int lengthsp = ORDER_fir_sp; // Fir length = ORDER_q
unsigned indexsp;

Iir1st averaging_(0.9998f); // 直流分を求めるためのフィルタ

struct Delaydc { float vn1dc, vn2dc; }; // IIR構造体2(LPF)
Delaydc vk_dc[N_SECTS_dc]; // 遅延器(LPF)


const int DS_RATE1_ = 60; // 1/60 にダウンサンプリング
Cic3Stage cic3I_(DS_RATE1_, A0_); // CIC フィルタ,I 信号用
Cic3Stage cic3Q_(DS_RATE1_, A0_); // CIC フィルタ,Q 信号用

const int DS_RATE2_ = 1; // 1/1 にダウンサンプリング(SSBでは使わない)
FirFastSymmetry lpfI_(ORDER_FIR_, HN_); // FIR フィルタ,I 信号用
FirFastSymmetry lpfQ_(ORDER_FIR_, HN_); // FIR フィルタ,Q 信号用

// 正弦波発生のための関数で使う定数(但しFo=1.5)
const float PI2_ = 6.283185f;
const float FS_DS=FS_/DS_RATE1_; //ダウンサンプリングによるFSレート変換
const float F0_m = 1.5025f; // 発生する正弦波の周波数:1.5 kHz
const float A1_m = 2.0f*cosf(PI2_*F0_m/FS_DS);
const float B1Sm = sinf(PI2_*F0_m/FS_DS);
const float B1Cm = -cosf(PI2_*F0_m/FS_DS);
float sinXm; //
float cosXm; //

BusOut bOut(D2, D3, D4); //速度測定のための出力端子 D2,D3,D4

// 正弦波発生のための関数
void Generate_m(float &sinXm, float &cosXm)
{
static float vn1m = 1.0f; // v[n-1]
static float vn2m = 0.0f; // v[n-2]

float vnm = A1_m*vn1m - vn2m;
sinXm = B1Sm*vn1m;
cosXm = vnm+B1Cm*vn1m;

vn2m = vn1m; // v[n-2] ← v[n-1]
vn1m = vnm; // v[n-1] ← v[n]
}

// 縦続形 IIR フィルタ(帯域制限1.3kHz)Weaver方式用に使う 複素FIR方式には不使用
float IirCascadelp(float xn, const Coefslp cklp[], float g0lp, Delaylp vklp[], int nSectslp)
{
float vn0;
float ylp = g0lp*xn;
for (int k=0; k<nSectslp; k++)
{
vn0 = cklp[k].a1lp*vklp[k].vn1lp + cklp[k].a2lp*vklp[k].vn2lp + ylp;
ylp = vn0 + cklp[k].b1lp*vklp[k].vn1lp + cklp[k].b2lp*vklp[k].vn2lp;

// 遅延器のデータの移動
vklp[k].vn2lp = vklp[k].vn1lp;
vklp[k].vn1lp = vn0;
}
return ylp;
}

// 縦続形 IIR フィルタ-2(帯域制限1.3kHz)Weaver方式用に使う 複素FIR方式には不使用
float IirCascadelp2(float xn, const Coefslp2 cklp2[], float g0lp2, Delaylp2 vklp2[], int nSectslp2)
{
float vn0;
float ylp2 = g0lp2*xn;
for (int k=0; k<nSectslp2; k++)
{
vn0 = cklp2[k].a1lp2*vklp2[k].vn1lp2 + cklp2[k].a2lp2*vklp2[k].vn2lp2 + ylp2;
ylp2 = vn0 + cklp2[k].b1lp2*vklp2[k].vn1lp2 + cklp2[k].b2lp2*vklp2[k].vn2lp2;

// 遅延器のデータの移動
vklp2[k].vn2lp2 = vklp2[k].vn1lp2;
vklp2[k].vn1lp2 = vn0;
}
return ylp2;
}

// 縦続形 IIR フィルタ(DC抽出--1Hz-バターワース)---AMやSSBのDC抽出機能
float IirCascadedc(float qn, const Coefsdc ckdc[], float g0dc, Delaydc vkdc[], int nSectsdc)
{
float vn0;
float yndc = g0dc*qn;
for (int k=0; k<nSectsdc; k++)
{
vn0 = ckdc[k].a1dc*vkdc[k].vn1dc + ckdc[k].a2dc*vkdc[k].vn2dc + yndc;
yndc = vn0 + ckdc[k].b1dc*vkdc[k].vn1dc + ckdc[k].b2dc*vkdc[k].vn2dc;

// 遅延器のデータの移動
vkdc[k].vn2dc = vkdc[k].vn1dc;
vkdc[k].vn1dc= vn0;
}
return yndc;

}

// 高速化 FIR フィルタ(複素フィルタ)  複素FIR Real の実行
float FirDirect(float wn, const float hk[], float Buff_[], unsigned *index, unsigned length)
{

float yn;
unsigned i;
Buff_[*index]=wn;
Buff_[*index+length]=wn;
*index=*index+1;
if (*index==length) *index=0;
yn=0.0;
for (i=0; i<length; i=i+1) yn=yn+hk[i]*Buff_[*index+i];
return yn;

}

// 高速化 FIR フィルタ(複素フィルタ)  複素FIR Image の実行
float FirDirectsp(float un, const float hksp[], float Buff_sp[], unsigned *indexsp, unsigned lengthsp)
{

float ynq;
unsigned i;
Buff_sp[*indexsp]=un;
Buff_sp[*indexsp+lengthsp]=un;
*indexsp=*indexsp+1;
if (*indexsp==lengthsp) *indexsp=0;
ynq=0.0;
for (i=0; i<lengthsp; i=i+1) ynq=ynq+hksp[i]*Buff_sp[*indexsp+i];
return ynq;

}

// AD 変換終了割り込みに対する割り込みサービス・ルーチン
void AdcIsr()
{


static int count = 0;

float xn = adc_.Read(); // ADC の読込み
float sinX, cosX;
nco_.Generate(sinX, cosX); // sin, cos 発生

// cos/sin を乗算したデータに対する CIC フィルタの積分処理
cic3I_.Integrate((int16_t)(xn*cosX)); // I 信号
cic3Q_.Integrate((int16_t)(xn*sinX)); // Q 信号



if (++count >= DS_RATE1_)
{

count = 0;
NVIC->STIR |= EXTI3_IRQn; // ソフトウェア割り込み発生

}

}

// EXTI3_IRQ に対する割込みサービス・ルーチン
// CIC フィルタのくし形フィルタの処理と FIR フィルタの入力の処理
void IsrCIC()
{

// static int count = 0;

bOut = 0; //所要時間測定スタート---------------------------追加

float yI_n = cic3I_.CombFilter(); // くし形フィルタ
float yQ_n = cic3Q_.CombFilter(); // くし形フィルタ

//For Complex Filter Method

float yn = FirDirect(yI_n, HK_, Buff_, &index, length); // ComplexFilter
float ynq = FirDirectsp(yQ_n, HK_sp, Buff_sp, &indexsp, lengthsp); //
float zo=yn+ynq;


//*************************** AGC 用計算***************************************
// float zsq=sqrtf(yn*yn+ynq*ynq); //AMDET---
// float yndc=IirCascadedc(zsq, CK_dc, G0_dc, vk_dc, N_SECTS_dc);//DC抽出
// float gain = 0.8f/yndc;
// if (gain > 200.0f) gain = 200.0f;
//*******************************************************************************


//***************************FOR Weaver Method *******************************
//// xn=yI_n;
// zn=yQ_n;
// float ylp = IirCascadelp(xn, CK_lp, G0_lp, vk_lp, N_SECTS_lp); //
// float ylp2 = IirCascadelp2(zn, CK_lp2, G0_lp2, vk_lp2, N_SECTS_lp2); //
// Generate_m(sinXm,cosXm); //1.5kHz生成(1.44uS)
// zo=ylp*10.0f*cosXm+ylp2*10.0f*sinXm; //合成

//******************** for AM RADIO using INTERRUPUT EXIT 4 ****************
// lpfI_.Store(yI_n); // FIR フィルタのバッファへ保存,I 信号----yI_n:--I_Phase
// lpfQ_.Store(yQ_n); // FIR フィルタのバッファへ保存,Q 信号----yQ_n:--Q_Phase
// if (++count >= DS_RATE2_) //---これはSSBでは起動しない
// {
// count = 0;
// NVIC->STIR |= EXTI4_IRQn; // ソフトウェア割り込み発生
// }
//********************************************************************************
dac_.Write(10.0f*zo); //復調出力
bOut = 1; //所要時間測定ストップ
}

// EXTI4_IRQ に対する割込みサービス・ルーチン(SSBでは不使用)
// FIR フィルタの実行とAM復調の処理
void IsrFIR()
{
//bOut = 0; // 所要時間測定スター

// float yI_n = lpfI_.Execute(); // FIR フィルタによる LPF 実行
// float yQ_n = lpfQ_.Execute(); // FIR フィルタによる LPF 実行
// float yn = sqrtf(yI_n*yI_n + yQ_n*yQ_n); // 包絡線成分を求める
// float dc = averaging_.Execute(yn); // 直流分を求める
// yn = yn - dc; // 直流分除去

// float gain = 0.8f/dc;
// if (gain > 200.0f) gain = 200.0f;
//dac_.Write(gain*yn);//
//bOut = 1; // 所要時間測定ストップ
}

// シリアル・ポートの受信割込みに対する割り込みサービス・ルーチン
void RxIsr()
{
unsigned char chr = pc_.getc();
//-----------------------Orijinal Tuning----------------------------
// 受信した文字が '0' ~ '6' の場合のみ NCO の周波数を変える
// if (('0' <= chr) && (chr <= '6'))
// {
// nco_.Set(F_C_[chr & 0x07]);
// pc_.putc(chr); // エコーバック
// }
// if (chr == '\r') pc_.printf("\r\n");
//--------------------------------------------------------------

//以下はキーボードでNCO周波数を変更する
if (chr == '1'){Freq_R=Freq_R+0.1e3f;
nco_.Set(Freq_R);}
if (chr == '0'){Freq_R=Freq_R-0.1e3f;
nco_.Set(Freq_R);}
//int Freq_disp=Freq_R;
pc_.printf("%f",Freq_R); // 周波数エコーバック これを整数にするとクリック音大!
pc_.printf("\r\n");

}

int main()
{
InitialMessage("ダウンサンプリングに CIC フィルタと FIR フィルタを使用.", AM, pc_);

NVIC_SetPriority(ADC_IRQn, 0); // 最優先
NVIC_SetPriority(EXTI3_IRQn, 1); // 2番目に優先
NVIC_SetPriority(USART2_IRQn, 2); // 3番目に優先
NVIC_SetPriority(EXTI4_IRQn, 3); // 4番目に優先

// ADC に対する割り込みサービス・ルーチンの設定
adc_.SetIntrVec(&AdcIsr);

// EXTI3 によるソフトウェア割り込みに対応する設定
NVIC_SetVector(EXTI3_IRQn, (uint32_t)IsrCIC);
NVIC_EnableIRQ(EXTI3_IRQn);

// EXTI4 によるソフトウェア割り込みに対応する設定
NVIC_SetVector(EXTI4_IRQn, (uint32_t)IsrFIR);
NVIC_EnableIRQ(EXTI4_IRQn);

// シリアル・ポートの受信割込みの設定
pc_.format(); // default: 8 bits, nonparity, 1 stop bit
pc_.attach(&RxIsr);

while (true) {}
}