FPGA | TamLab 授業のおもちゃ箱 https://tamlab.fc2.page 情報系科目の講義資料とツールを公開 Sat, 17 Jun 2023 04:52:47 +0000 ja hourly 1 https://wordpress.org/?v=6.7.2 【MEMSマイクアレイ】 データ収集システム 同期信号の追加 https://tamlab.fc2.page/category-electronic-work/category-fpga/1495/ https://tamlab.fc2.page/category-electronic-work/category-fpga/1495/#respond Sat, 04 Feb 2023 11:01:31 +0000 https://tamlab.fc2.page/?p=1495 多数のマイクロフォンを並べた「マイクロフォンアレイ」のデータを収集するシステムで,送信データに同期用信号を追加できるように変更しました。また,FIFOメモリの使用効率を考えて,内蔵FIFOの形式を,スピーカアレイ駆動システム VHDLコード (2) (FIFO内蔵版)で採用していた64ビット入力8ビット出力から,8ビット入力8ビット出力に変更しました。

さらに,今回のシステムでは,L/R切り替え機能を利用してマイクロフォンの入力数を2倍に増やす仕組みを組み込み,接続可能なマイクロフォンの個数は96としています。

注意:

<注意を読む>

この投稿は,未完成のシステムの開発過程の備忘録として書かれたものです。紹介されているコードにはバグや冗長な部分が含まれています。このコードを参考にする場合は注意をお願いします。また,解説の文章などに誤りや意味不明の箇所が含まれていることにも配慮してください。

<閉じる>

同期信号の必要性

データ送信の過程では,通信が途切れたり,送信していないデータが付加されたりするなど,エラーが避けられません。エラーのため送信したデータと受信したデータでデータの開始時点がずれてしまうと,全く異なるデータが受け取られてしまいます。

このようなデータの同期のずれを防ぐため,データを“フレーム”と呼ぶ単位に分割して送信し,フレームとフレームの間に同期用の信号を挿入します。

マイクの個数が64と異なる場合の問題

64ビット入力8ビット出力のFIFOを使ったシステムは,入力マイク数が64の場合は,シンプルな構成でデータ収集が可能でした。しかし,マイク数が64より大きくなると対応できません。また,マイク数が64より少ない場合,FIFOメモリの中で使わない領域ができてしまいます。

<詳しく読む>

実はFIFOを使うシステムは早い時期に試していました。しかし,送信データが「文字化け」するという問題がありました。この原因はFPGA基板内部で特定の信号ピンがプルアップされているためであることが後で明らかになりました(【MEMSマイクアレイ】 MachXO2評価基板信号ピンのプルアップ)。しかし,当初はそれが不明だったため,チャンネルセレクタの切り替えタイミングの問題を疑って,構成がシンプルな64ビット入力8ビット出力FIFOを使うシステムを試してみました。

文字化けの原因が明らかになったので,チャンネル数を可変にできるチャンネルセレクタと8ビット入力8ビット出力FIFOを使う構成で開発を進めることにしました。

<閉じる>

システムの変更点

今回の変更は,以下のようなことを目指しています。

  • 同期信号の追加
    指定されたチャンネル数のデータに1バイトの同期信号を付加して送信するようにした。
  • チャンネル数の変更を容易にする
    FIFOを8ビット入力にすることで,チャンネル数を変更しやすくした。
  • ピン配置を固定にする
    これまでのシステムは,マイクのチャンネル数を変えるとハードウェア構成も変更されピンの再割り当てが必要だった。今回は,完全なハードウェア固定まではできていないが,ピン割り当ては固定できるようにVHDLコードを修正した。

ハードウェアの構成

データ収集システムのハードウェアの概略の構成を以下の図に示します。接続可能なMEMSマイクの最大個数は96としてあります。細かな制御信号は略しています。

ハードウェアの構成

MEMSマイクの出力は8ビットごとに1つにまとめて1チャンネルとして扱います。MEMSマイクのL/R切り替え機能を利用するためマイクの出力2つを1つの入力ピンに接続します。したがって入力ピンは1チャンネルあたり4つになります。またこの構成図では,MEMSマイクのL/R制御入力は,偶数番目のマイクにはVdd,奇数番目のマイクにはGNDを接続することにしています。

この入力信号は,3MHzのクロックの立上りエッジで偶数番号,立下りエッジで奇数番号のマイクの出力がサンプルされたものになります。この信号をコンポーネントMEMS_INで処理することで,3MHzクロックの立上りエッジに同期した1チャンネル当たり8ビットのマイク信号に復元します。復元した信号の名前を,これまでのVHDLコードのマイク入力と同じmic_inにしておけば,コードの変更箇所は少なくなります。

復元されたmic_inは複数(最大12)チャンネルの信号なので,チャンネルセレクタを介して8ビット幅のFIFO入力ポートに接続します。

チャンネルセレクタの入力にはマイクロフォンのための信号チャンネルに加え同期用に1チャンネルを追加しました。マイクロフォンのチャンネル数をCh_nとすると,インデックスが0番からCh_n-1番のチャンネルがマイクロフォン用,インデックス-1のチャンネルが同期用になります。

チャンネルセレクタにより,初めにチャンネル-1の同期用データが,その後に0~Ch_n-1のデータが,順に8ビット幅のFIFO入力に書き込まれます。FIFO出力ポートも8ビット幅で,これをUSBインタフェースICのFT232Hの入力ポートに接続しています。

]]>
https://tamlab.fc2.page/category-electronic-work/category-fpga/1495/feed/ 0
【MEMSマイクアレイ】 MachXO2評価基板信号ピンのプルアップ https://tamlab.fc2.page/category-electronic-work/category-fpga/1479/ https://tamlab.fc2.page/category-electronic-work/category-fpga/1479/#respond Fri, 03 Feb 2023 23:51:35 +0000 https://tamlab.fc2.page/?p=1479 評価基板

現在,MEMSマイクアレイ用データ収集システムは,Mach XO2評価基板(MachXO2 Breakout Board Evaluation Kit)を使っています。その際,評価基板のFPGAの入力ピンの一部がプルアップされている仕様に気付かなかったため結構な時間を費やしてしまいました。この投稿では,そのトラブルと評価基板のピンに関する注意点について整理します。

動作チェックまでの手順のおさらい

動作チェックまでの手順をおさらいしておきます。

ハードウェアの構築(Lattice Diamondを使用)

  1. VHDLコードを入力・編集
  2. JEDECファイルを作成
  3. ピン割り当て
    Spreadsheet ViewのPin AssignmentsタブからFile > Export > Lattice CSVファイル によりピン配置のCSVファイルをエクスポートする。これをピン配置用のExcelファイルで読込み編集してからCSVファイルを出力し,Spreadsheet ViewでインポートしてLattice Diamondのプロジェクトに取り込む。
  4. IO属性の設定
    Spreadsheet ViewのPort Assignmentsタブを開き,入出力ピンのPull Up/Pull Downの設定などをする。デフォルトの設定では入力ピンは全てPull Downになっている。タクトスイッチによるリセット用と動作開始用の入力ピン(VHDLコードでの信号名はRESET_SWとSTART_SW)はPull Upに指定する。なお,Pull Down,Pull Upともに抵抗値は公証で10kΩとなっている。
  5. 書き込み(Program)

動作チェック

  1. テスト信号の設定
    確認したい内容に合わせてテスト用入力信号を設定する。例えば,チャンネル0のビット0に対応するマイク入力ピン(信号名mic_in[0][0])をVddに接続する。これによりチャンネル0の信号は常に“1”(0000 0001)というデータとして受信されるはず。
  2. テスト用C++コードでデータ受信

上記の設定で受信したデータを10進数で表示すると,以下のようなデータが受信されています。左側の欄が受信データの番号,右側が受信データです。

冒頭(受信番号0)で“0”が受信されています。おそらく,書込まれたものではなく出力レジスタの初期データと考えられます。次に受信される番号1の“1”がテスト用信号として設定したチャンネル0の値と考えられます。この3つ後ろの4番目のデータはチャンネル3のデータです。設定では入力ピンはプルダウンになっているので“0”となるはずですが,“12”(2進数では0000 1100)となっています。さらに,ここに示したデータでは,“1”と“12”は周期8で出現していきますが,ときどき周期がずれることがわかりました。どこかでデータが欠落したり逆に追加されたりしている可能性があります。

また,卒研の学生さんが使っているピン割り当てが異なる別の基板では,チャンネル3以外のところに“96”(1100 0000)という値が現れています。これらのことから,以下のような問題があることがわかります。

  • 受信されたデータに設定していない値が現れる
  • 受信データの周期が8からずれることがある

この時点で設定していない値が特定の2つの入力ピンと関連していることに気付いていれば,評価基板のユーザガイドを見直したはずです。しかし,

  • 構築したハードウェアの問題ではないか
    例えば,データ転送のロジック回路の設計に問題がある。論理シミュレーションでは問題なく動いているので,実装した際の回路の問題,例えば論理回路のハザードやジッタ,グラウンドバウンスの影響かもしれない。FPGAとUSB転送インタフェースFT232Hをジャンパワイヤで接続しているため雑音に弱いので,そのせいか?
  • USB転送の仕様の問題
    何かイベントコードを追加しているのではないか?

などと,いろいろ考えて設計の見直しやテストを繰り返しました。64ビット入力8ビット出力のFIFOを使う,よりシンプルなハードウェアにしても同じ問題が起きた段階で,やっと気が付きました。

2つの信号ピンが外部でプルアップされていた

以下はMach XO2評価基板のユーザガイドに載っている基板の回路図です。中ほどに配置されたU3-2と表記されているICがMach XO2 FPGAです。注目していただきたいのは,125番126番の2つのピンが2.2kΩの抵抗でプルアップされていることです。

この2つのピンは汎用の信号ピンまたはI2Cインタフェース用の信号ピン専用に設定することができます。そして,125番ピンはI2CのSDA,126番ピンはI2CのSCLとして使うように基板上で2.2kΩの抵抗で電源Vddにプルアップされていました。

2つのピンは開発中のシステムではmic_in[3][3]とmic_in[3][2],つまりチャンネル3のビット3と2に対応しています。Lattice Diamondのピン割り当てでは,FPGA内部で公称値10kΩでプルダウンする設定になっていても,外部で2.2kΩでプルアップされていては,ピンに何も接続していない場合は“1”が入力されることになります。

これが,チャンネル3のデータが“12”になった原因です。そこで,入力ピンmic_in[3][3]とmic_in[3][2]をGNDに接続すると,“12”ではなく“0”が受信されるようになり,このことが確認できました。

評価基板の設定で注意すべきこと

評価基板に搭載されてるFPGAのピンは,ユーザが自由に使えないものもあることはユーザガイドで把握していました。しかし2つのピンが基板上でプルアップされていたことは,ユーザガイドの末尾に載っていた回路図を見直すまで気が付きませんでした。

その他の評価基板でのピン割り当てについての注意点も併せて挙げておきます。

  • ヘッダJ2の19番,20番はI2C用に抵抗2.2kΩでプルアップされている
    プルアップされていることに留意すれば,汎用の入出力として使うことは可能と考えられます。
  • ヘッダJ2の25,26,29,30番はFPGAのJTAGプログラム専用に使われている
    この4つのピンは,Lattice Diamonndで設定できても,使うことはできません。
  • ヘッダJ3の31,32,35~40番ピンは基板上のLEDに接続されている
    汎用の入出力として使うことは可能と思われます。しかし,高速に変化する信号を扱う場合は,LEDが負荷になるので動的な特性が他のピンと異なってくる可能性があります。

以上のピンについては,ピン割り当て用のExcelファイルでは色分けなどにより注意できるようにしています。(ヘッダJ2の19番,20番については今後,反映させる予定)

なお,Lattice Diamondのピン割り当ては(当然ながら)評価基板上の接続やピンの役割の情報は反映されていません。FPGA内部だけでなく,基板上,さらに基板外の周辺回路との接続状況を読み取る仕組みについて卒業研究のテーマとして,概念設計まではしたことがあります。

残された問題

今回の「データ化け」の問題は,回路図の見落としという,恥ずかしい原因で起きました。開発や工作で出会うトラブルのほとんどは,思い込みで発生します。今回も「開発ソフトで,プルダウン設定にしたから大丈夫」という勝手な思い込みが原因となって,少なくない時間を消費してしまいました。

一方,受信データの周期がずれる問題は残っています。この「フレームずれ」の問題は,送信データに同期用の信号を追加することで対応することにしました。既にハードウェア構成を変更して動作も確認したので,別稿で報告することにします。また,論理回路のハザードやジッタ,雑音が影響としていると考えられる不安定な挙動も見られますので,今後,原因を詰めて対策を考えていく予定です。

]]>
https://tamlab.fc2.page/category-electronic-work/category-fpga/1479/feed/ 0
【MEMSマイクアレイ】 マイクの個数を2倍にする https://tamlab.fc2.page/category-electronic-work/category-fpga/1358/ https://tamlab.fc2.page/category-electronic-work/category-fpga/1358/#respond Mon, 09 Jan 2023 06:32:37 +0000 https://tamlab.fc2.page/?p=1358 MEMSマイクのステレオ対応機能を利用する

現在のデータ収集システムでは接続できるマイクロフォンの個数が64個に制限されています。この制限は,使っているFPGA評価ボードの入出力ピンの個数によるものです。

しかし接続可能なMEMSマイクの個数の上限を現在の2倍にすることが可能です。システムで採用しているMEMSマイクSPM0405D4H(KNOWLES ACOUSTICS社)には,2個のマイクを組み合わせてステレオマイクにするための入力ピンが備えられています。これを利用します。

本稿では,ステレオ機能を利用してマイクの個数を2倍にするためのFPGA内のハードウェアの変更について解説します。

注意:

<注意を読む>

この投稿は,未完成のシステムの開発過程の備忘録として書かれたものです。内容は随時変更・追加されます。また,紹介されている内容には誤りや冗長な部分が含まれています。参考にする場合は注意をお願いします。また,解説の文章などに誤りや意味不明の箇所が含まれていることにも配慮してください。

<閉じる>

MEMSマイクのステレオ機能

Fig.1に示すように,SPM0405D4HにはL/Rという名前の入力端子が備えられています(株式会社秋月電子通商,MEMSマイクの技術資料より)。

Fig.1 SPM0405D4Hの信号ピン (株式会社秋月電子通商の資料より)

L/RをGNDに接続すると出力ピンはクロックの立下り時にデータが有効になり,立上りでハイインピーダンス(電子的に何も接続されていない状態)になります。一方L/RをVddに接続すると,クロックの立上がり時にデータが有効になり,立下りでハイインピーダンスになります(株式会社秋月電子通商,MEMSマイクの技術資料2より)。

Fig.2 タイミングの仕様

クロックエッジから出力データが有効になるまでの遅延時間tdvの値は最小20ns最大40nsと規定されています。一方,ハイインピーダンスになるまでの遅延時間tdzの最大値は15nsです。

ステレオ機能を利用する場合,L/RをGNDに接続したマイクとVddに接続したマイクの出力を1つの入力ピンに接続します。データ有効遅延時間tdvとハイインピーダンス遅延時間tdzの規定値からわかるように,2つのマイクのデータが有効となる期間は重ならないようになっています。したがって2つのマイクのデータが衝突することはありません。その結果,入力ピンで得られる信号は2つのマイクの出力がクロックの立上りと立下りのエッジに同期して交互に現れることになります。

システムの変更

ステレオマイク対応機能を利用したシステムを設計していきます。まだVHDLコードの改造にはとりかかっていませんので,設計の過程について解説していきます。VHDLコードの内容や実測については結果が出しだい投稿内容を追加・更新していきます。

ブロック線図とタイミングチャートで考える

まず紙と鉛筆を用意します。そして設計するシステムのブロック線図を,それから,タイミングチャートを描いて考えています。(本当は手描きのブロック線図とタイミングチャートの原図を示せるとよいのですが,私の筆圧が弱すぎるし文字も汚なすぎるので,非公開です。)

このとき,信号名なども決めていきます。設計したハードウェアは最終的にVHDLなどのハードウェア記述言語コードで実装するので,信号名をどうするかは結構重要です。

今回紹介するのは設計というほど大層なものではないのです。内部信号の名前付けやデータ構造を決めるだけです。もちろん,これはとても大事な作業です。「アルゴリズム+データ構造=プログラム」という名著があるくらいで,信号をどう表現するかはシステム全体の設計に大きな影響を与えます。さらに以下に示すように,設計に際して大前提となる基準があります。

  • 動作が確認されている既存のハードウェアを基にする
    基のハードウェアの構造や内部信号のデータ構造を大きく変えることは避ける。また,追加するハードウェアの規模がなるべく小さくなるようにする。
  • 信号の命名規則やデータ構造をわかりやすくする
  • 配線長などハードウェア的な条件も考慮する
    命名規則やデータ構造を決めるときにマイクロフォンの番号付けが物理的な並び方とかけ離れたり,配線長が長くなったりしないように気を付ける。(ただし,これはハードウェア記述言語による設計には直接関係はしない。)

これらの条件を考え,今回は,基となるFPGAの入力部分を変更することにしました。基のデータ収集ハードウェアではマイク入力をmic_in[0][0]~mic_in[0][7],mic_in[1][0]~mic_in[1][7],・・・のように表しています。この入力部分をステレオ対応の多重化入力信号を復号しmic_in[0][0]~mic_in[0][7],mic_in[1][0]~mic_in[1][7],・・・を出力する部分に置き換えます。こうすると,mic_in[n][m]から後の処理は基のハードウェアのものをそのまま利用できます。

ブロック線図

紙と鉛筆でいろいろ考えて決定したブロック線図を示しましょう。(本当は,この「いろいろ考える」ところが楽しいし,学生さんに伝えたいのですが,残念ながら今回は割愛します。)

まず,基になるハードウェアの入力部分です。

Fig.3 基のハードウェアの入力部分

全てのMEMSマイクのL/R端子は共通のレベル(ここではVdd)に接続します。次に,新しく追加するハードウェアのブロック線図です。いろいろ変更しました(2023/1/13)

Fig.4 入力部分のブロック線図

以下のようにしています。

  • 隣接するマイク2つをペアとしてステレオ接続する
    隣接するマイクの番号が1だけ異なるように番号付けする。そして,マイク0番とマイク1番の出力を接続した信号をMEMS_MIC[0][0],2番と3番の出力を接続した信号をMEMS_MIC[0][1],・・・のように表現する。
  • 入力信号を立上りと立下りエッジで動作するレジスタで受ける
    立上りエッジで入力信号を保持する(“ラッチする”)レジスタRegP,立下りエッジでラッチするレジスタRegNを用意し,図のように接続する。

このようにして信号mic_in[0][0]~mic_in[0][7],mic_in[1][0]~mic_in[1][7],・・・を得ています。

タイミングチャート

それではタイミングチャートを使って考えています。回路全体のタイミングチャートは最後に示すことにして部分的に見ていきましょう。ブロック線図の最上段,MEMS_MIC[0]の処理(Fig.5)を例に説明していきます。

Fig.5 MEMS_MIC[0][0]からmic_in[0]とmic_in[0][1]を分離する

Fig.6に2つのマイクMIC 0とMIC 1の出力を接続したラインの信号MEMS_MIC[0][0]を処理する回路のタイミングチャートを示します。信号CLKはMEMSマイクに供給されるHとLの割合が等しいDuty比50%のクロック信号です。立上りと立下りの時間が0の理想的なクロック信号として描いています。なお,ハイインピーダンス状態はレベルHとLの中間のレベルのラインで表しています。

Fig.6 MEMS_MIC[0][0]のタイミングチャート

MIC 0はL/R端子をVddに接続しているので,CLKの立上りエッジから20ns~40nsの間に有効データを出力します。そしてクロックの立下りエッジの後15ns以内にハイインピーダンスに切り替わります。説明のために,MIC 0の出力する有効データをP0,P1,P2,・・・という時系列で表しています。

一方,MIC 1はL/R端子をGNDに接続しているので,CLKの立下りエッジから20ns~40nsの間に有効データを出力し,立上りエッジの後15nsまでにハイインピーダンスになっています。有効データにN0,N1,N2,・・・と名前をつけておきます。つまり,MIC 0のCLKのk番目の立上りエッジの後の有効データをPk,この立上りエッジの直後の立下りエッジで有効になるMIC 1のデータをNkとしています。

このようにMEMS_MIC[0][0]の出力にはP0,N0,P1,N1,P2,N2,・・・のように,接続された2つのマイクの有効データが交互に現れることなります。実際には有効データの間に,両方のマイクの出力が同時にハイインピーダンスになる時間区間(最短で5nsの幅)があります。しかし,描くのがメンドくさいので,タイミングチャートでは省いて表示します。

<詳しい説明を見る>

時間軸を拡大したタイミングチャートを詳しく見てみましょう。

Fig.6a 時間軸を拡大

MIC 0の出力はCLKの立下りエッジから遅れ時間15ns以内でハイインピーダンスに切り替わります。これに対してMIC 1の出力はCLKの立下りエッジの20ns後に有効データになります。Fig.6aの遅れ時間は「最悪」の条件である「ハイインピーダンスの切り替えが最も遅れ,逆に有効データが最も早く出力された場合」のものです。この場合でも,MIC 0の出力する有効データはMIC 1の有効データと衝突することはありません。その結果,MIC 1とMIC 2の出力を接続した配線上の信号MEMS_MIC[0]はクロックの立上りエッジの20ns後から立下りエッジの15ns後までの間はMIC 0の出力P0となります。

5nsの時間幅で両方のマイクの出力がともにハイインピーダンスになる区間がありますが,Fig.6のタイミングチャートでは描いていません。

大事なのは,信号MEMS_MIC[0]は,CLKの立上りエッジではMIC 1の出力,立下りエッジではMIC 0の出力の値になっていることです。

<閉じる>

次に,この多重化されたMEMS_MIC[0]の信号からMIC 0の信号P0,P1,P2,・・・とMIC 1の信号,N0,N1,N2,・・・を分離する方法を説明します。

信号MEMS_MIC[0]をCLKの立上りエッジでラッチした信号regPと,CLKの立下りエッジでラッチした信号regNのタイミングチャートを次に示します。それぞれP0。P1,P2,・・・とN0,N1,N2,・・・に分離されていることがわかります。ただし信号が切り替わるタイミングはP0。P1,P2,・・・はCLKの立下りで,N0,N1,N2,・・・は立上りになっています。P0,P1,P2,…系列の方がクロック周期の半分だけ早いタイミングで切り替わっています。

そこで,regNをCLKの立上りエッジでもう1度ラッチすると,下のタイミングチャートのように,切り替わりのタイミングの揃った信号としてmic_in[0][0]とmic_in[0][1](= regP)が得られます。

Fig.7 mic_in[0][0]とmic_in[0][1]のタイミングを合わせる

最後にタイミングチャート全体を示します。

Fig.8 タイミングチャートの全体

Fig.4のブロック線図を基にVHDLなどのハードウェア記述言語で回路を設計していきます。ただし,HDLによる回路記述でブロック線図中のレジスタは,明示的に生成する必要はないことに注意してください。

VHDLコードを作りかけたところで,ブロック線図や信号の名称を変更した方がよいのではないかと考え直しました。設計では,このように当初の設定に遡って変更することがあります。前回の投稿を書き直さないで残しておいた方が,実際の設計の過程を紹介するためには良かったかもしれません。でもページ数が増えてしまうので,書き直しました。

VHDLコード

ブロック線図ができたので,それを基にVHDLコードを作っていきます。コードは動作確認のためトップレベルの回路として記述します。最終的には,ハードウェアのコードの中に直接埋め込むか,コンポーネントとして組み込むことにします。

入出力

講義の「お手本」通りに,まず入力信号と出力信号を決めます。ここでは最大で96個のマイクを接続可能なシステムを作ることにします。

  • 入力信号
    マイクは2個ずつペアにするので,マイク用入力信号は全部で96/2 = 48になる。これをチャンネル当たり4ビットの信号12チャンネルとし,
    MEMS_MIC(0)(0)~MEMS(0)(3),
    MEMS_MIC(1)(0)~MEMS(1)(3),
    ・・・
    MEMS_MIC(11)(0)~MEMS(11)(3)
    のように,2次元配列MEMS_MIC(n)(m) (n=0~11,m=0~3)で表す。
  • 出力信号
    出力は96ビットになる。これをチャンネル当たり8ビットの信号12チャンネルとし,
    mic_in(0)(0)~mic_in(0)(7),
    mic_in(1)(0)~mic_in(1)(7),
    ・・・
    mic_in(11)(0)~mic_in(11)(7)
    のように,2次元配列mic_in(n)(m) (n=0~11,m=0~7)で表す。

上のように信号を2次元配列で表現したのは,後々のメンテナンスのためです。入力を48ビット幅,出力を96ビット幅のstd_logic_vectorで表しても,性能の点では変わらないと思います(厳密には回路合成系によるかもしれませんが)。また,ビット幅の広いstd_logic_vector型を使った方がVHDL記述はシンプルになるでしょう。しかし,シミュレーションや実機での試験では,8ビット幅のチャンネル単位で区切った方が扱いやすくなります。

アーキテクチャ

Process文1個で作ることにします。ブロック線図からわかるように,単純な構造の回路をチャンネル数だけ生成することになるので,VHDLのloop構文を使って記述します。

コードの例

パッケージファイルの内容を示します。

LIBRARY ieee;
USE ieee.std_logic_1164.all;
package ARRAY_PKG is
  constant N_CH : integer := 12;
  constant N_BYTE : integer := 1;
  subtype SIG is std_logic_vector (N_BYTE*8-1 downto 0);
  type SIG_ARRAY is array (0 to N_CH-1) of SIG; 
  subtype MEMSIG is std_logic_vector (N_BYTE*4-1 downto 0);
  type MEMSIG_ARRAY is array (0 to N_CH-1)of MEMSIG; 
end ARRAY_PKG;
  • constant N_CH : integer := 12;
    チャンネル数12とする。
  • constant N_BYTE : integer := 1;
    チャンネル当たりのバイト数は1のまま。マイクの個数はN_CH×N_BYTE×8=96となる。
  • subtype MEMSIG is std_logic_vector (N_BYTE*4-1 downto 0);
    MEMSマイクの入力は1チャンネル当たり4ビットになる。

次はMEMSマイクの出力を2個ずつ組みにした信号を入力し,これを各マイクの出力に分離する回路のVHDLコード,MEMS_INを示します。

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;
USE ieee.std_logic_unsigned.all;
USE work.ARRAY_PKG.all;

ENTITY MEMS_IN IS
	PORT(
			mems_mic 	:  	in 	MEMSIG_ARRAY;
			mic   		:  	out SIG_ARRAY;
			reset     	:  	in 	STD_LOGIC;
			clk			:  	in 	STD_LOGIC
		 );
END MEMS_IN;

ARCHITECTURE behavior OF MEMS_IN IS
signal regN : MEMSIG_ARRAY;
BEGIN
PROCESS(clk,reset) 
BEGIN
	IF(reset = '0')THEN
		regN <= (others => (others =>'0'));
		mic <= (others => (others =>'0'));
		ELSIF(clk'EVENT AND clk = '1')THEN
			for n in 0 to N_CH -1 loop
				for m in N_BYTE*4 -1 downto 0 loop
					mic(n)(2*m)   <= regN(n)(m);
					mic(n)(2*m+1) <= mems_mic(n)(m);
				end loop;
			end loop;
			ELSIF(clk'EVENT AND clk = '0')THEN
				for n in 0 to N_CH -1 loop 
					regN(n) <= mems_mic(n);
				end loop; 
	END IF;		
END PROCESS;
END behavior;
  • PORT(
    mems_mic : in MEMSIG_ARRAY;
    mic : out SIG_ARRAY;
    reset : in STD_LOGIC;
    clk : in STD_LOGIC
    );
    入力信号の名前を変更している。MEMSマイクのペアの出力をクロックの立上りと立下りで二重化した入力信号をmems_mic,これを立上りのエッジに同期した通常の2つの信号に変換した出力をmicとしている。
  • IF(reset = ‘0’)THEN
    regN <= (others => (others =>’0′));
    mic <= (others => (others =>’0′));
    レジスタのリセットをしている。
    regN:立下りエッジでラッチする複数のレジスタの信号を表す12チャンネル×4ビットの2次元配列。
    mic:出力信号を表す12チャンネル×8ビットの2次元配列。
    これらの2次元配列に一括して同じ値を書き込む場合,
      (others => (others =>’0′))
    のような記述を使う。
  • ELSIF(clk’EVENT AND clk = ‘1’)THEN
    ・・・・
    ・・・・
    ELSIF(clk’EVENT AND clk = ‘0’)THEN
    ・・・・
    ・・・・
    クロックの立上りエッジでラッチする回路とクロックの立下りエッジでラッチする回路の記述。これらのELSIF構文の中に,forを使った繰り返しにより複数のラッチを生成している。
シミュレーション

コードができたら論理シミュレーションで生成される回路の動作確認をします。HDLを使ったハードウェア開発では,シミュレーションは必須のツールです。

コンピュータのソフトウェア用のプログラミング言語とは異なり,HDLはハードウェアを生成するための言語です。ソフトウェア開発用の言語では,デバッグモードで動作させ,プログラムの中にブレークポイントを設定するなどして動作の追跡と変数の値の確認ができます。

一方,HDLを使った開発では,生成するハードウェアの動作確認をシミュレーションで実行します。信号はタイミングチャートとして表示されます。また,信号がある条件を満たしたときに記録を開始することも可能です。

テストベンチ

シミュレーションでは,ハードウェアに入力する信号を生成したり,ハードウェア内部の信号や出力信号の変化を観測したりするハードウェアである「テストベンチ」を記述します。実物のハードウェアの動作を確認するための信号発生器やオシロスコープ,ロジックアナライザなどのセットのようなものと考えてください。

今回のハードウェア,MEMS_INのHDL記述も,シミュレーションにより基本的な動作は確認済みです。使用したテストベンチのVHDLコードは,テストされるMEMS_INのコードより大きなものになっています。作るのに要した時間も長くなってしまいました。表示されるタイミングチャートがわかりやすくなるように考えたのですが,開発者「ヤじるし」の知識不足のせいで思わぬところで足踏みしてしまたせいです。

テストベンチのVHDLコードを示します。

LIBRARY ieee;
USE ieee.std_logic_1164.all;
--USE ieee.std_logic_arith.all;
USE ieee.numeric_std.all;
USE ieee.std_logic_unsigned.all;
USE ieee.std_logic_textio.all;
LIBRARY STD;
USE std.textio.all;
USE work.ARRAY_PKG.all;

-- シミュレーションでは,テストベンチがトップレベルとなる
ENTITY testbench IS 	-- テストベンチのENTITYは外部への入出力が無い
END testbench;			-- したがってPORT宣言も無い
-- テストベンチのアーキテクチャ記述・・・
ARCHITECTURE sim OF testbench IS
	constant	CYCLE		: TIME	:= 16.67 ns; 	-- 60MHzクロックの周期
	signal 		mic_out 	: SIG_ARRAY; 			-- Mic 1bit output	
	--signal 		mic_out 	: array (0 to N_CH-1)of std_logic_vector(7 downto 0);  
	signal 		mems_out 	: MEMSIG_ARRAY; 		-- Signal of paired mics	
	signal 		mic_signal 	: SIG_ARRAY; 			-- converted from mems_out
	signal 		reset_sw  	: STD_LOGIC	:= '1';		-- リセットスイッチ
	signal     	start_sw   	: STD_LOGIC	:= '1';		-- スタートスイッチ
	signal 		clk60MHz	: STD_LOGIC	:= '1'; 	-- from FT232H
	signal 		clk_3MHz 	: STD_LOGIC;
	signal 		cnt_3M 		: STD_LOGIC_VECTOR(7 downto 0) := (others => '0');
	signal 		cnt 		: STD_LOGIC_VECTOR(7 downto 0) := (others => '0');

-- テストしたいハードウェアのトップレベルをCOMPONENTにする	     
COMPONENT mems_in
	PORT(
			mems_mic 	:  	in 	MEMSIG_ARRAY;
			mic   		:  	out SIG_ARRAY;
			reset     	:  	in 	STD_LOGIC;
			clk			:  	in 	STD_LOGIC
	     );
END component;

-- component instance テストしたいハードとテストベンチのsignalを接続する
BEGIN
mems_in_test : mems_in
	PORT MAP(
				mems_mic => mems_out,
				mic => mic_signal,
				reset => reset_sw,
				clk => clk_3MHz
			 ); 
			 -- ここから,テスト用signalを生成していく   
--Generate 60MHz clk 60MHzクロックを生成
PROCESS -- sensitivity listが無いので無条件に,ずっと繰り返す
BEGIN
	wait for CYCLE/2;
	clk60MHz &lt;= not clk60MHz;
END PROCESS;

PROCESS(reset_sw, clk60MHz) -- 60MHz → 3MHz 
BEGIN
	IF(reset_sw ='0')THEN
		cnt_3M  &lt;= (others => '0');
		clk_3MHz &lt;='0';
	ELSIF(clk60MHz'EVENT AND clk60MHz='1')THEN
		IF(cnt_3M &lt; 19)THEN
			cnt_3M &lt;= cnt_3M + 1;
			IF(cnt_3M &lt; 10)THEN
				clk_3MHz &lt;= '0';
			ELSE
				clk_3MHz &lt;= '1';
			END IF;
		ELSE
			cnt_3M  &lt;= (others => '0');
		END IF;
	END IF;
END PROCESS;
   
PROCESS --RESET信号(アクティブロー)を作る
BEGIN
	wait for CYCLE/2;	-- 初期値の1をCYCLE/2だけ保持してから
	RESET_SW &lt;= '0';	-- 0に落とし
	wait for 10 ns;		-- 10ns保持し
	RESET_SW &lt;= '1';	-- 1に戻す
	wait;				-- このwaitを入れると,PROCESSを1回だけ実行
END PROCESS;			-- つまり,この後は,ずっと 1を保持

--START信号(アクティブロー)を作る この信号で動作を開始させる
PROCESS
BEGIN
	wait for 30ns;		-- RESET信号が済んでからSTART信号を生成
	START_SW &lt;= '0';	-- 0にする
	wait for 20ns;		-- 20nsの間0を保持してから(20nsでなくてもOK)
	START_SW &lt;= '1';	-- 再び1に戻す
	wait;				-- この後は,ずっと 1を保持
END PROCESS;

PROCESS(reset_sw, clk_3MHz)
BEGIN
	IF(reset_sw = '0')THEN
		cnt &lt;= (others => '0');
		ELSIF(clk_3MHz'EVENT AND clk_3MHz = '1')THEN
			cnt &lt;= cnt +1;
	END IF;
END PROCESS;

PROCESS(reset_sw, clk_3MHz)
BEGIN
	IF(reset_sw = '0')THEN
		for i in 0 to N_CH-1 loop
			mic_out(i) &lt;= std_logic_vector(TO_unsigned(i,8));
		end loop;
	ELSIF(clk_3MHz'EVENT AND clk_3MHz = '0')THEN
		for i in 0 to N_CH-1 loop
			mic_out(i) &lt;= std_logic_vector(unsigned(mic_out(i))+TO_unsigned(1,8));
		end loop;
	END IF;
END PROCESS;

PROCESS(reset_sw, clk_3MHz)
BEGIN
	IF(reset_sw ='0')THEN
		mems_out &lt;= (others => (others => '0'));
		ELSIF(clk_3MHz'EVENT AND clk_3MHz = '1')THEN
			for n in 0 to N_CH-1 loop
				for m in 0 to N_BYTE*4 -1 loop
					mems_out(n)(m) &lt;= mic_out(n)(2*m);
				end loop;
			end loop;
		ELSIF(clk_3MHz'EVENT AND clk_3MHz = '0')THEN
			for n in 0 to N_CH-1 loop
				for m in 0 to N_BYTE*4 -1 loop
					mems_out(n)(m) &lt;= mic_out(n)(2*m+1);
				end loop;
			end loop;
	END IF;
END PROCESS;
END sim;

MEMS_INに入力するテスト信号は以下のようなものを設定しました。

  • リセットが入力されると
    チャンネル 0は0000 0000(10進数の0)
    チャンネル 1は0000 0001(10進数の1)
    ・・・
    チャンネル11は0000 1011(10進数の11)
    と初期化する
  • クロックの立下りに同期して各チャンネルのデータを1つず増加する

実際のマイクの出力は,上のような挙動を示すことはありえません。MEMS_INが意図した通りに動いていることを確認するために,このようなテスト信号を作りました。

シミュレーションの結果

シミュレーションで得られた信号波形を示します。ModelSIMのウインドウの画像に説明用の文字や図形を朱書きで追加しています。また,画面内に信号の全部のチャンネルを収めるため,チャンネル数を12ではなく8に減らした結果を示しています。

信号が3MHzクロックの1.5周期だけ遅れて復元されていることがわかります。このシミュレーションにより,立上りと立下りのエッジに同期して二重化された48個の信号から2倍の数の96個の信号を復元できることが確認できました。

サンプリング時刻のずれの影響

今回扱っているシステムでは,クロックの立上りでサンプリングするマイクと立下りでサンプリングするマイクが混在してます。つまり,サンプリング時刻が3MHzのクロック信号の半周期分だけずれた2系統の信号が存在します。

後述するように,通常の音声用マイクとしての応用では,このサンプリング時刻のずれはほとんど影響がないと考えられます。しかし,ここで紹介しているデータ収集システムは,指向性の形成や雑音除去などの高度な処理に使うことを想定しているので,サンプリングの時刻のずれの影響を評価しておく必要があります。

サンプリング時刻のずれの補償

音響信号を対象とする限り,全ての信号を同じ時刻にサンプリングした形に変換することは可能です。システムで使うMEMSマイクは3MHzで音響信号をサンプリングしています。「サンプリング定理」によると,対象とする信号に含まれる周波数の上限がサンプリング周波数の1/2までなら信号を復元できます(理論的には)。ですから,3MHzの半分,1.5MHzまでの周波数の信号であれば復元できるので,サンプリング時刻のずれの補償はできそうです。

<詳しく読む>

ただしMEMSマイクはΔΣ変調を使っているので,単純に3MHzをサンプリング周波数として考えることはできません。復元された信号に混入する量子化雑音の影響も考慮する必要があります。したがって,シミュレーションなどによる検証が必要となります。

それでも,量子化雑音込みの音響信号をサンプリング時刻を合わせた形で復元可能なので,2系統のマイクで得られた信号を,仮想的に1つの系統(例えば3MHzクロックの立上り)でサンプリングされた信号に統一することができます。

<閉じる>

何もしなくても問題ない?

2つのサンプリング系列で得られた信号を1つのサンプリング系列で得られた信号を得ることが原理的に可能としても,計算が必要なのでコストがかかります。何もしないで,立下りエッジでサンプリングされた信号を立上りでサンプリングされたものとして扱うとどうなるか考えてみます。

この場合,復元された信号は元の信号から少し「ずれた」ものになります。信号が正弦波信号だとすると,位相誤差が生じることになります。この誤差を見積もってみましょう。

扱う音響信号の周波数の上限{f_{max}}を人間の可聴周波数の限界と言われる20kHz,サンプリング周波数を{f_s}としましょう。サンプリング間隔{\Delta t}1/{f_s}となります。

{\Delta t /2}だけずれてサンプリングされた信号は,ずれがなくサンプリングされた場合と比べて位相が{2\pi f_{max} \Delta t /2}だけ異なっています。

{f_s}= 3\times 10^6

f_{max}= 20\times 10^3

とすると,位相誤差は,

{2\pi f_{max} \Delta t /2} = 2\pi  f_{max}/f_s /2

= 2\pi \frac { 20\times 10^3}{(3\times 10^6)/2 } = 2\pi \times (0.33 \times 10^{-2})

となります。位相誤差をどこまで許容するかの基準は用途によって異なります。しかし,例えば2 \pi /100以下を1つの基準とすることができるでしょう。つまり位相1回転をフルスケールとして,その1%以下までを許容誤差とする考え方です。上の計算では2\pi \times (0.33 \times 10^{-2})ですから,0.33%となります。3MHzのクロックの立上りと立下りでサンプリングしたものを同時刻でサンプリングしたものとして扱っても,問題なさそうです。(もちろん,高いSN比を要求される用途では,シミュレーションや実測で評価する必要があります。)

]]>
https://tamlab.fc2.page/category-electronic-work/category-fpga/1358/feed/ 0
【MEMSマイクアレイ】 データ収集システム FIFO内蔵型に変更 https://tamlab.fc2.page/category-electronic-work/category-fpga/1259/ https://tamlab.fc2.page/category-electronic-work/category-fpga/1259/#respond Sat, 26 Nov 2022 15:00:00 +0000 https://tamlab.fc2.page/?p=1259 システムの構成

多数のマイクロフォンを並べた「マイクロフォンアレイ」のデータを収集するシステムにFIFOを追加するなどの改造をしました。

システムの問題点

始めに作製した,FIFOを内蔵しないシステムの構成については「【MEMSマイクアレイ】 データ収集システムの構成」を参考にしてください。このデータ収集システムの動作について簡単にまとめると次のようになります。

  • 64個のMEMSマイクの出力は3MHzのクロックに同期して同時にサンプルされる
  • サンプルされたデータをバイト単位で転送する
    64ビットのデータをバイト単位でまとめたものを“チャンネル”と呼ぶ。64ビットのデータを8つのチャンネルとして扱い,ch[0]~ch[7]のように0~7のインデックスで区別する。
  • サンプルされたデータはチャンネル単位で転送される
    ch[0],ch[1],ch[2],・・・ch[7],ch[0],ch[1],・・・という順番で周期的にUSB転送ICのFT232Hに入力する。FT232Hは深さ1kBのFIFOを持ち,これを介してUSBインタフェースによりデータを送信する。

ところが,動作確認すると,以下のような問題があることがわかりました。

  • 受信したデータにマイク入力とは異なるデータが混ざる
    この「文字化け」は,ほぼ8バイトの周期で起こる。問題なのは,その周期が常に8バイトというわけではなく,ばらつくことである。
  • 受信データの開始がずれる
    受信されたデータはch[0]から始まるわけではなく,その開始チャンネルのインデックスも試行ごとに異なっている。
  • データが途切れる
    受信されたデータは時間サンプルの番号が連続しているわけではなく,飛び・データの欠落があるる。

以上のような問題があるため,指定したMEMSマイクの信号を抽出することが難しくなっています。例えばMEMSマイクからの入力信号のmic_in[0][0]は0番のマイクの出力に対応します。データの欠落がなければ,mic_in[0][0]を検出した時点から受信データを8個置きに取り出していけばマイク0番が出力するオーディオデータを抽出できます。しかし,データの欠落や飛びがあると途中から別のマイクのデータになってしまいます。

現在,MEMSマイクは1個しか接続していません。とりあえずこの1個のマイクのデータをPCに取り込んで,1ビット信号をマルチビット信号に変換し,マイクの感度や雑音などの技術的な問題を洗い出そうと考えました。しかし,データの開始位置のずれや欠落があると,それができません。

また,「文字化け」がどのような場合にどのような形で起きるのかを明らかにする必要がありました。これをしておかないと取得されたデータの信頼性が大きく損なわれます。(実はこの「文字化け」の原因は突き止めたのですが,詳しいことは別稿で解説する予定です。)

注意:

<注意を読む>

この投稿は,未完成のシステムの開発過程の備忘録として書かれたものです。紹介されているコードにはバグや冗長な部分が含まれています。このコードを参考にする場合は注意をお願いします。また,解説の文章などに誤りや意味不明の箇所が含まれていることにも配慮してください。

<閉じる>

対策

文字化けやデータ欠落などの症状の原因を探るためにも,症状の現れ方には再現性があった方が望ましいのです。はじめに作ったシステムは,とりあえず動けばよいという考えで作ったものでした。このため信号の遅延や雑音の影響はあまり考慮していません。FPGA基板とUSB転送ICであるFT232Hとの接続もブレッドボード上でジャンパワイヤを使っていました。そこで,様々な不確定要素を減らすために,以下の3つの対策をしてから文字化けやデータ欠落の原因を探ることにしました。

対策1 FPGA内部にFIFOを作る

FIFOを内蔵することで,FT232Hへのデータ送信を途切れさせないようにできる。さらに,USB転送とのタイミングのずれを吸収することになる。動作を安定させるためにはFIFOの搭載は必須であると考え,FIFOを組み込むことにした。

対策2 ピン割り当ての変更

 クロック信号はクロック専用ピンに割り当てるようにFPGAのピン配置を変更した。また,配線長がなるべく短くなるように,FPGAのピン割り当ても変更した。 

対策3 ブレッドボードから専用基板に変更
ブレッドボードとジャンパワイヤでの相互接続を止め,FT232Hを搭載した基板をFPGA基板に2段重ねすることにした。これにより配線長が大幅に減少する。また,配線の接続状態の確認も容易になる。

ハードウェアの構成

データ収集システムのハードウェアは,図のように構成されています。

MEMSマイクの出力を64ビット幅の入力ポートを持つFIFOに入力します。一方,FIFOの出力ポートは8ビット幅になっていて入力されたデータの下位バイトから順に出力されます。このため以前の【MEMSマイクアレイ】 データ収集システムの構成で使っていたチャンネルセレクタなどの回路は不要になりました。ただし,入力マイク数は64に固定になってしまうなど,使い勝手のよくないところもあります。

]]>
https://tamlab.fc2.page/category-electronic-work/category-fpga/1259/feed/ 0
【FT232H】D2XXドライバのインストール https://tamlab.fc2.page/category-electronic-work/category-fpga/1184/ https://tamlab.fc2.page/category-electronic-work/category-fpga/1184/#respond Thu, 20 Oct 2022 04:19:00 +0000 https://tamlab.fc2.page/?p=1184 マイクロフォンアレイやスピーカアレイのハードウェアでは,FTDI社(Future Technology Devices International Ltd.,以下FTDIと略)のUSBインタフェースICであるFT232Hを使ってPCとデータのやりとりをしています。本稿では,FT232H用のソフトウェアドライバのインストールの方法について解説します。合わせて,WindowsのVisual C++によるソフトウェア開発についても説明します。

<注意を読む>

この投稿は,未完成のシステムの開発過程の備忘録として書かれたものです。記述の内容の誤りやコードやのバグや冗長な部分が含まれている可能性があります。内容を参考にする場合は注意をお願いします。また,解説の文章などに誤りや意味不明の箇所が含まれていることにも配慮してください。

<閉じる>

参考資料

以下のサイトと文献を参考にしました。

ドライバとは

本稿でのドライバとは,FT232Hインタフェースチップを動かすためのデバイスドライバ,つまりUSBで接続されたFT232Hを制御するためのソフトウェア群のことです。C++言語でFT232Hを操作するプログラムを作るためにはデバイスドライバが必要となります。このデバイスドライバはFT232Hの製造メーカーであるFTDIが提供しています。

デバイスドライバは「縁の下の力持ち」で,C++言語などでアプリケーションを作るユーザが触れることはありません。

<詳しく読む>

Windows CDM driverの構造

ここでインストールするのはCDM(Combined Driver Model)driverと呼ばれるもので,上の図のような層状の構造になっています。層の一番上がアプリケーション層で,簡単にいうとC言語などで作ったプログラムになります。このアプリケーション層が2つの部分に分かれていることに注意してください。COM PortアプリケーションとD2XXアプリケーションです。

図の一番下の層はUSBホストを制御するためのドライバで,コンピュータのOSにすでにインストールされているものです。ですから一番上から下から2層目までがFTDIの提供するドライバになります。

下から2番目の層はFTDIBUS.SYSというドライバで,COM PortとD2XXのどちらを使う場合でもインストールされます。COM PortアプリケーションはFTSER2K.SYSというドライバを使ってFTDIBUS.SYSとやり取りします。一方,D2XXアプリケーションはFTD2XX.DLLというダイナミックリンク型のドライバを使ってFTDIBUS.SYSとやり取りします。

アプリケーションが一度に通信できるのは2つのインターフェースのうち1つだけで、D2XXとCOMポートに同時にコマンドを送信することはできないことに留意してください。

<閉じる>

今回はD2XXアプリケーションの開発について解説します。

ドライバーとライブラリのダウンロード

Windowsマシンの場合,FT232H基板とPCをUSBケーブルで接続することによりドライバが自動的にインストールされます。プラグインの前にドライバを新しくインストールする場合は以下で説明する手順にしたがって実行します。またC++コードなどでプログラムを作成する場合は,手動インストール用のライブラリファイルも必要となります。

D2XXドライバのインストーラとライブラリファイルをダウンロードする場合,以下のページ

D2XX driverのダウンロードページ

にアクセスし,OSとプロセッサアーキテクチャで分類された表のところまでスクロールダウンしてください。

ダウンロードページにある表

表の一番上にWindowsデスクトップマシン用のソフトウェアのダウンロードリンクがあります。対応するOSはWindows 7, Windows 8/8.1, Windows 10, Windows 11です。

  • セットアップ用実行形式ファイルをダウンロード
    右端の“setup executable”をクリックしてzip形式ファイルをダウンロードする(現時点でファイル名はCDM212364_Setup.zip)。これを解凍してできるフォルダ内にあるCDM212364_Setup.exeがドライバをセットアップするための実行形式ファイルになる。(このファイルを以下ではインストーラと呼ぶことにします。)
  • プロセッサに対応した手動インストール用ライブラリをダウンロード
    Intel系64bitCPUの場合はX64(64-Bit)の欄をクリック。CDM v2.12.36.4 WHQL Certified.zipというファイルがダウンロードされるので,これを解凍する。解凍されたフォルダの中にライブラリのファイルが格納されている。(以下,このフォルダをライブラリフォルダと呼ぶことにします。)
ライブラリフォルダCDM v2.12.36.4 WHQL Certified)の内容

ドライバのインストール

  • インストール
    CDM212364_Setup.exeをダブルクリックして起動することで,インストールが実行される。ダイアログボックスが表示されるので,それに従う。

<詳しく読む>

インストーラCDM212364_Setup.exeの実行によって,ライブラリフォルダの中のファイルがWindows OS用のフォルダの中にコピーされます。ライブラリフォルダの中で,32bitアプリ用のファイルはi386という名前のフォルダ,64bitアプリ用はamd64というフォルダの中に格納されています。インストーラの実行後で調べてみると,ライブラリフォルダの中のdllファイルは,次のようにWindows OS用フォルダのにコピーされていました。

  • 32bit用dll(i386の中のftd2xx.dll)
    C:\Windows\SysWOW64の中にコピー。
  • 64bit用dll(amd64の中のftd2xx64.dll)
    C:\Windows\System32の中にコピー。ただし,ファイル名はftd2xx.dllに変更されている。

<閉じる>

デバイスマネージャの確認とCOMポートの停止

ドライバが正しくインストールされたかどうかの確認をします。これにはWindowsの「デバイスマネージャ」を使います。OSによりデバイスマネージャの開き方が少しずつ異なるので,webで調べてください。

PCとFT232H基板をUSBケーブルで接続し,デバイスマネージャを開きます。ドライバが正しくインストールされている場合は,次の図のようなデバイスツリーが表示されます。

インストール後のデバイスツリー
USB Serial Portを無効にする

ポート(COMとLPT)というカテゴリの下のUSB Serial Port,ユニバーサルシリアルバスコントローラの下のUSB Serial Converterという,2つのデバイスがあります。これを確認できたら,インストールが正常にできたことが確認できます。

次に,USB Serial Portを無効にします。私の環境では,これをしないとD2XXドライバを使うプログラムでFT232Hチップを認識できませんでした。(デバイスのEEPROMを書き換えることで対応できるのかもしれませんが,まだ調べてはいません。)

デバイスツリーでUSB Serial Portを選択して右クリックし,ドロップダウンメニューから“デバイスを無効にする”を選びます。これでデバイスを無効にできます。

]]>
https://tamlab.fc2.page/category-electronic-work/category-fpga/1184/feed/ 0
【MEMSマイクアレイ】FT232H用C++コード(1) https://tamlab.fc2.page/category-electronic-work/category-fpga/933/ https://tamlab.fc2.page/category-electronic-work/category-fpga/933/#respond Sun, 16 Oct 2022 04:57:00 +0000 https://tamlab.fc2.page/?p=933 マイクを多数並べたマイクロフォンアレイ用のデータ収集システムを作製しています。このシステムのハードウェアは,以下の3つの要素で構成されています。

  • FPGA
  • USBインタフェースFT232H
  • PC

1ビット出力のMEMSマイクをFPGAの入力ピンに接続し,これをUSBインタフェースICのFT232Hを介してPCに転送しています。

  • FPGA内部の回路はVHDLコードARRAY_MIC.vhdで記述した
    このハードウェアはFT232Hの容量1kバイトのFIFOにバイト単位でデータを書き込む。
  • PC側のC++コードはFT232Hからのデータ受信と保存を行う
    動作確認のため指定したマイクロフォンの1ビットデータをモニタに表示する。バイト単位で受信したデータはそのままテキストデータとして保存する。
  • 1ビットデータからマルチビットデータへの変換はMATLABコードで実行
    保存されたデータからマルチビットの波形への変換はMATLABコードで実行している。

本稿では,C++コードについて説明します。

コードの概略の構成

C++コードは以下のような処理を実行しています。

VHDLコード全体は,概略,以下のような構成になっています。

ヘッダファイルの指定,
FT232Hを使う準備

パラメータの設定

データ読み込み

データの保存

ReadFromMEMS_Mic.cppの構成

includeするヘッダファイルを指定。

FTDI社から提供されているftd2xx.hを指定する点に注意。

マイクロフォンの個数や保存するデータの長さなどのパラメータを設定。

データを読み込む。

データをテキストファイルとして保存。動作確認用にモニタ画面に実際に読み込まれたバイト数を表示する。

それでは,ソースコードの上から順番に説明していきます。

ヘッダファイルの指定,デバイスを使う準備

ヘッダファイルの指定

#include <windows.h>
#include <stdio.h> 
#include "ftd2xx.h" //ftd2xxドライバを使うためのヘッダファイル

3行目の#include “ftd2xx.h”で,コメントにもあるように,FT232Hのドライバを使うためにインクルードするヘッダファイルを指示します。(ftd2xxドライバのインストールについては,別の投稿でまとめる予定です。)

FT232Hを使う準備

8行目付近から40行目付近まででFT232Hを使う準備をしています。順番に説明していきます。

// FT232Hを使う準備 ///////////////////////////////////////////////
	FT_HANDLE fthandle1;	// FT232Hデバイスのハンドル変数
	FT_STATUS status;		// ドライバ関数実行時のステータス変数
	// FT232H基板上のFT232HデバイスのIDを設定
	// 対応するデバイスに応じてコメントを外す
		//PVOID id = (PVOID)"FT63ZE1L"; // 田村のシステム
		//PVOID id = (PVOID)"FT6MVP8N"; // 工藤君のシステム
	PVOID id = (PVOID)"FT723GDQ"; // 田村のシステム 小林君用
  • FT_HANDLE fthandle1; // FT232Hデバイスのハンドル変数
    FT_STATUS status; // ドライバ関数実行時のステータス変数
    変数型FT_HANDLEとFT_STATUSは,ft2xx.hの中で定義されている。
    FT_HANDLE型のfthandle1は,FT232Hデバイスを指定するための“識別番号”のような変数。
    FT_STATUS型のstatusは,ft2xxドライバが提供する関数を実行したときに,実行の失敗や成功などの“状態”を返すための変数。
  • PVOID id = (PVOID)”FT723GDQ”; // 田村のシステム 小林君用
    PVOID型は,個々のFT232Hチップに固有の識別子を表すための変数型である。この識別子はFT_ProgというプログラムでUSBバスに接続したチップから読み取ることができる。ここでは”FT723GDQ”という識別子を読み取っていたので,それをPVOID型に型変換してidという変数に代入している。

コメントアウトされている,
status = FT_Open(0, &fthandle1);
は,FT232Hチップ固有のIDを指定しないでデバイスをオープンする関数です。しかし,PCに複数のUSBインタフェースを接続して動作させる際に,エラーが発生することがあったので,チップのIDを指定してオープンする関数,
status = FT_OpenEx(id, FT_OPEN_BY_SERIAL_NUMBER,&fthandle1);
を使うようにしました。変数idによって表されるIDは長さ8の文字列です。FTDI社が提供するアプリケーションソフトFT_progを使って調べました。(おそらく,ft2xxライブラリの中にある関数でも調べることができるはずですが,ここではFT_progで調べた値を使っています。)

	// デバイスをオープンしハンドルfthandle1を取得
	// ハンドルというのは,プログラム内でのデバイスのアドレスみたいなもの
	status = FT_OpenEx(id, FT_OPEN_BY_SERIAL_NUMBER, &amp;fthandle1);
		// デバイス固有のIDを使わない場合は次のコマンドでOK
		//status = FT_Open(0, &amp;fthandle1)	;// 変数fthandleにハンドルの値が入る 
		
	// ハンドル取得に失敗した場合は,プログラム終了  
		if (status != FT_OK)
		{
			printf("open device status not ok %d\n", status);
			return 0;
		}
  • status = FT_OpenEx(id, FT_OPEN_BY_SERIAL_NUMBER, &fthandle1);
    FT_OpenEx関数を呼び出してfthandle1というハンドル型変数の値を取得している。
    id:先に取得したPVOID変数
    FT_OPEN_BY_SERIAL_NUMBER:シリアルナンバーであるidを使ってオープンするという指示
    &fthandle1:変数fthandle1にハンドルの値を書き込ませるため,&fthanfle1と“&”を付けて,fthandle1へのポインタを引数としている。
    関数の戻り値のstatusには,関数呼び出しが正常に実行されたかどうかなどの情報が書き込まれる。
  • if (status != FT_OK)
    ・・・
    関数呼び出しが正常に実行された場合は,statusの値はFT_OKになっている。そうでない場合は,何かの原因でデバイスを認識できなかったということなので,プログラム実行を終了しする。なお,他のFT232H関連の関数呼び出しでは,FT_OKでない場合のプログラム終了のコードを省いている。コードの安全性の点では問題あり。
	// readとwriteのタイムアウト時間をmsec単位で設定
		status = FT_SetTimeouts(fthandle1, 10000, 500);
		if (status != FT_OK)
			printf("timeout device status not ok %d\n", status);
  • status = FT_SetTimeouts(fthandle1, 10000, 500);
    タイムアウトを設定している。なんらかの原因でFT232Hの動作が遅れた場合に,設定した時間が経過したら実行終了を待たずに関数から戻るようにする。タイムアウトの時間はms単位で指定する。この場合,受信と送信のタイムアウト時間をそれぞれ10000msと500msに設定している。この設定値が適切かどうかはまだ不明。
	// デバイスのモードを設定(入力,Synchronous FIFOモード)
		UCHAR MaskA = 0x00; // Set data bus to inputs
		UCHAR modeA = 0x40; // Configure FT232H into 0x40 Sync FIFO Mode
		status = FT_SetBitMode(fthandle1, MaskA, modeA);
		if (status != FT_OK)
			printf("mode A status not ok %d\n", status);

		Sleep(500);  // 500msだけスリープ
  • UCHAR MaskA = 0x00; // Set data bus to inputs
    FT232HのデータバスD7~D0は入力か出力かをビット毎に設定できる。そのためのマスクがUCHAR型(ftd2xx.hで定義されていて,実際は8ビット幅の符号なしキャラクタ)のMaskAという変数になる。このC++コードの目的はFT232Hのバスは8ビット全部を入力として使うので,MaskAは16進表記の00,つまり全てのビットを0にしている。
  • UCHAR modeA = 0x40; // Configure FT232H into 0x40 Sync FIFO Mode
    modeA変数も8ビット幅で,FT232Hの動作モードを設定するために使う。この値を16進の40に指定するとSynchronous FIFOモードに設定される。Synchronous FIFOモードは60MHzのクロックに同期してFIFOデータにデータを読み書きするモードで最大40MB/sの転送速度が実現できる。
  • Sleep(500); // 500msだけスリープ
    モード設定に時間がかかる可能性があるので,500msのスリープを挿入。

パラメータの設定

// パラメータの設定 ///・・・というコメント行から80行付近までは,データ収集に関する様々なパラメータの設定をしています。

// パラメータの設定 /////////////////////////////////////////////////////
	const int N_CH = 8;		// VHDLコードの「チャンネル数」
	const int N_BYTE = 1;	// VHDLコードの「チャンネル当たりのバイト数」
	DWORD RxBytes;// 読み出しバイト数を格納する変数
	DWORD TxBytes;// 書き込みバイト数を格納する変数
	DWORD EventDword;// FT232Hのイベントを表現する変数
	setvbuf(stdout, (char*)NULL, _IONBF, 0); //現在,未使用
	int num;	// 1回に読み込むデータの総バイト数
	// 1回の読み込みの総バイト数をUSBパケットの最大に設定
	num = 1024*64; // USBパケットの最大長
	int loop_n = 4; // 繰り返し数,変更して総データ数を調節
	int nTime = num * loop_n/N_CH/N_BYTE;// 総時間サンプル数
	
	// 入力バッファ用配列を宣言
	UCHAR* data_in = new UCHAR[num]; // 1回の転送データ用の配列
	UCHAR* data_buf = new UCHAR[num*loop_n];// 全データ用の配列
	//getchar();

	// 受信および送信のFIFOバッファを空にする
	status = FT_Purge(fthandle1, FT_PURGE_RX | FT_PURGE_TX);
	if (status != FT_OK)
		printf("status not ok %d\n", status);

	// レイテンシタイマを設定 この値が適切かどうかは不明
	status = FT_SetLatencyTimer(fthandle1, 2);
	if (status != FT_OK)
		printf("status not ok %d\n", status);
		
	// USBパラメータのセット
	  // 転送サイズは64バイトの整数倍(64~64Kバイト)
	  status = FT_SetUSBParameters(fthandle1, 0x10000, 0x10000);
	  // 送受信とも最大値0x10000 = 2^16 = 65536(64K)に設定
		if (status != FT_OK)
			printf("status not ok %d\n", status);

		UCHAR* data_out = new UCHAR[nTime]; //波形の出力配列を確保
		int* bytes_read = new int[loop_n];// 読込バイト数の確認用
		DWORD data_read, r_data_len ;

読めば理解できると思いますが,少しややこしい部分もあるので,以下に抜粋して解説します。

  • const int N_CH = 8; // VHDLコードの「チャンネル数」
    const int N_BYTE = 1; // VHDLコードの「チャンネル当たりのバイト数」
    N_CHとN_BYTEはFPGAの回路を記述しているVHDLコードの変数に合わせる。
  • int num; // 1回に読み込むデータの総バイト数
    FT232H用の関数ではUSB転送の1回で読み込むデータが決まっている。numは1回に読み込むデータの総バイト数。
  • num = 1024*64; // USBパケットの最大長
    FT232H用のライブラリのドキュメントで読むと,1回のデータ転送のバイト数は転送サイズは64バイトの整数倍(64~64Kバイト)となっている。そこでnum は最大値1024*64に設定している。この設定が妥当かどうかは今後検討する必要がある。
  • status = FT_SetUSBParameters(fthandle1, 0x10000, 0x10000);
    FT_SetUSBParametersによりUSB転送のバイト数を設定する。受信・送信ともに16進の10000つまり1024*64に設定している。(この部分は,固定値ではなくnumを設定するとそれに応じて変更されるように修正すべき。)
  • int loop_n = 4; // 繰り返し数,変更して総データ数を調節
    1回のデータ転送では検出できる音響信号の長さが不十分なので,データ転送をループで繰り返している。その繰り返し回数をloop_nという変数で指定する。この値を変更することで,記録できるデータの長さを調節する。
]]>
https://tamlab.fc2.page/category-electronic-work/category-fpga/933/feed/ 0
【MEMSマイクアレイ】 データ収集システムの構成 https://tamlab.fc2.page/category-electronic-work/category-fpga/942/ https://tamlab.fc2.page/category-electronic-work/category-fpga/942/#respond Sat, 15 Oct 2022 09:32:00 +0000 https://tamlab.fc2.page/?p=942 システムの構成

多数のマイクロフォンを並べた「マイクロフォンアレイ」のデータを収集するシステムを紹介します。元々は,「多数のスピーカやマイクロフォンをびっしり並べたものを使うと,面白いことができそうだ」ということでスタートした一連の研究のために必要になりました。

この研究を実際に動くハードウェアを作って実験的に進めることを考えます。一般的なアナログのマイクロフォンと電子回路を使う場合は,数十個,数百個のマイクロフォンの1つ1つの出力を増幅回路に接続し,AD変換してデータを集めることになります。このようなアナログ回路を使う構成では,マイクロフォンの個数が増えるとコスト的に大変です。

そこで,MEMSMicro Electro Mechanical System)技術を用いるマイクロフォン(SPM0405)に注目しました。この製品は,機械的な構成要素であるマイクロフォン,増幅回路,アナログ信号を1ビット信号に変換するΔΣ変調器の全てを,シリコン基板上に一体化して形成したものです。(秋月電子通商で購入しました。)

このデジタル出力のMEMSマイクを使えば,外付けのアナログ回路を一切使わないフルデジタルの処理が可能になります。また,デジタル回路を入出力ピン数の多いFPGAField Programmable Gate Array)で受信して処理することで,部品数が減り,回路の変更も容易になります。

今回紹介するのは,FPGAはデータをバイト単位で転送するシンプルな処理しかしていません。実は,1ビット信号をマルチビット信号に変換するSinc3フィルタをFPGA内部に実装したものも試作しました。しかし回路規模が大きくなってしまい,処理するマイクロフォンの個数が30個程度でハードウェアがFPGAに収まり切らなくなりました。このハードウェアについては別の機会に紹介します。

注意:

<注意を読む>

この投稿は,未完成のシステムの開発過程の備忘録として書かれたものです。紹介されているコードにはバグや冗長な部分が含まれています。このコードを参考にする場合は注意をお願いします。また,解説の文章などに誤りや意味不明の箇所が含まれていることにも配慮してください。

<閉じる>

データ収集システムのハードウェアは,図のように3つの部分で構成されています。

アレイスピーカ駆動システム
  • FPGA
    MEMSマイクの出力ピンからのデータをUSBインタフェースFT232Hに中継する。Lattice Semiconductor社のFPGA,MachXO2を搭載した評価基板MachXO2ブレークアウトボードを使用している。
  • USBインタフェース
    FTDI社のUSBインタフェースICであるFT232Hを搭載したAdafruit社の FT232H Breakout基板を使用する。
  • PC
    FT232Hから送られてくるデータの受信,1ビット信号からマルチビット形式の音響データへの変換,保存などの処理を実行する。

動作の概要

現在,システムは最大64個までのMEMSマイクロフォンを処理できます。処理できるマイクロフォンの個数は,この2倍,128まで拡張できます。ただし,そのためにはFPGAと波形復元の処理を変更する必要があります。

マイクロフォン出力のサンプリング周波数は3MHzに固定されています。システムの動作は以下のようになっています。

  • FPGAによるMEMSマイク出力のサンプリングとインタフェース
    FPGAは,MEMSマイクとのインタフェース,データ送信のタイミングの調整,データ選択などを実行するハードウェアを構成する。
    FPGAは60MHzクロックを分周して得られた周波数3MHzのクロックでMEMSマイクの出力をサンプルする。1回のサンプル当たり最大64ビットまでのデータは,8個の8ビットレジスタにラッチ(保持)される。ラッチしたデータは,レジスタの番号順にFT232H内部のFIFOバッファに,60MHzのクロックに同期して出力される(Synchronous FIFOモード)。
  • FT232Hによるデータ送信
    FT232Hは,内蔵FIFOバッファのデータをUSBインタフェースを介してPCに送信する。またFT232H内部の発信回路が出力する60MHzのクロック信号をFPGAのクロック信号として供給する。
  • PCによるデータ受信
    PCはFT232Hを使ってFPGAと通信し,USBインタフェースで受信したデータを処理する。ソフトウェアは,データ受信部をC++コードにより,マルチビットデータの復元処理をMATLABコードにより,それぞれ作成した。

<続きを読む>

現在のシステムは,通信エラーによるデータの欠落や「ずれ」への対策は施されていません。通信エラーは必ず起こると考えるべきなので,いずれエラー対応の機能を組み込む必要があると考えています。

<閉じる>

]]>
https://tamlab.fc2.page/category-electronic-work/category-fpga/942/feed/ 0
【スピーカアレイ駆動システム】フレームの寸法 https://tamlab.fc2.page/category-electronic-work/category-fpga/838/ https://tamlab.fc2.page/category-electronic-work/category-fpga/838/#respond Thu, 01 Sep 2022 07:03:39 +0000 https://tamlab.fc2.page/?p=838 オーディオ信号のリアルタイム処理では,信号はある一定の長さのフレーム(frame)毎に処理されます。フレームの長さは,システムの性能に直接影響します。例えば,出力される信号は,入力される信号に対しフレームの長さだけ遅れて出力されます。また,フレームの長さだけデータを格納しておくためにメモリが必要になります。一方,高度な処理をする場合は,フレームの長さは長くなる傾向があるでしょう。

さらに,スピーカアレイ駆動システムでは,通常のオーディオ信号の処理に加えて注意すべきことがあります。このシステムでは,集束または拡散する音響波面を発生させるため,生成する複数の信号の間に相対的な遅延時間を与えます。このため,最終的な駆動信号を生成するために複数のフレームに渡る処理が必要となってきます。この稿では,スピーカアレイ駆動システムのフレーム長について考察します。

処理の流れ

現在,二次元ΔΣ変調を用いるスピーカ駆動システムの駆動信号生成には大きく分けて2つの手法が存在します。遅延処理の後で二次元ΔΣ変調をするか,二次元ΔΣ変調後の信号に遅延処理をするか,の2つです。

  • 遅延処理後に二次元ΔΣ変調
    オーディオ信号に遅延を与えて,集束または拡散する波面を発生させる信号を作ります。次に,このオリジナルの信号を二次元ΔΣ変調して時間と空間の両次元に関して密な1ビットの駆動信号としています。
    この構成は,オリジナルの信号を高いSN比で再現することができるというメリットがあります。一方,オリジナルの信号が高い周波数成分(空間または時間あるいは両方の次元)を持つ場合,二次元ΔΣ変調が不安定になることがあります。不安定になるとオリジナルとは全く異なる信号が生成されるてしまいます。このため,再現できる音場が制限されます。例えば,集束音場を作る際に焦点の方向の角度を大きくできなくなります。これがデメリットです。
  • 二次元ΔΣ変調に遅延処理
    遅延を与えない信号を二次元ΔΣ変調で変換した後で,この1ビット信号に相対的な遅延を与えて駆動信号にします。これは,安定に動作する条件下でのオリジナル信号を二次元ΔΣ変調してから,焦点形成のための遅延を与えることになります。
    この方法のメリットは,どのような遅延を与えても,二次元ΔΣ変調の動作には影響がないことです。このため,集束波面の発生の場合,視野角を広く設定することができます。一方,デメリットは,再現される波面のSN比が遅延処理後二次元ΔΣ変調より低下することです。
遅延処理後に二次元ΔΣ変調
二次元ΔΣ変調後に遅延処理

遅延処理

遅延処理について,直線上にスピーカを配置したアレイを使い,1点に集束する音場を発生する例について説明します。

収束音場を発生させるための遅延

図のような,直線上にスピーカを配置したアレイを考えます。このアレイを含む,ある平面内の音場のみを扱うことにします。

直線状アレイ

平面内の1つの点を,そこに音場が集束する点(焦点)として設定します。

以下では数式を使って説明します。スピーカが配置されている直線をx軸に,アレイの中心を通り対象となる平面内にz軸をとります。i番目のスピーカのx座標を{x_i},焦点の位置座標を{(x_i,z)}とします。

①まず,はじめに,全てのスピーカを同じ信号\displaystyle u(t)で駆動する場合を考えましょう。なお,説明を簡単にするため,波形u(t)は,非常に短い正弦波パルスで表しています。また,波形の時間原点,t = 0は,駆動信号の送信を始める時刻であるとします。つまり,t<0では信号は

i番目のスピーカから送信され,焦点として設定した点に到達する音響波{r_i(t)}は,

{r_i}(t) = u(t - {\tau _i})

と近似して書くことができます。ここで,{\tau _i}は,i番目のスピーカと焦点の間の音響伝搬時間で,スピーカの座標を{(x_i,0)},音響波の伝搬速度をcとすると,

{\tau _i} = \frac{{\sqrt {{{(x - {x_i})}^2} + {z^2}} }}{c}

となります。

受信される信号は伝搬距離に応じて減衰するので,スピーカの位置により異なります。しかし,この減衰の値はほぼ一定と考えて,数式中では略しています。(詳しいことは,別稿で説明する予定です。)

①全てのアレイを同じ信号で駆動
②各スピーカから設定ポイントに到達する受信波

設定した点で観測される受信波は,各スピーカから到達する波形の総和になります。図の②に示したような状況では,スピーカのすべてを同じ信号で駆動すると,各スピーカから到達する信号波形は,遅延時間が互いに異なっています。このため,波形が打ち消しあって,観測される受信波の振幅は小さなものになります。

③そこで,伝搬時間を補償して送信することを考えます。u(t)に対して伝搬遅延時間の符号を逆にした遅延を与えた信号を,駆動信号とします。この信号で駆動されると,各スピーカから放射され設定点に到達する音響波は,全て同じ時刻に到達します。この結果,設定された点に音響波が集束することになります。

{u_i}(t) = u(t) + {\tau _i})   (i = 1,2, ...,N)

この駆動信号は{u_i}(t)は,図③の赤色で表されています。これからわかるように時刻t<0で値を持つので,実際には作ることができません。(時刻t=0より前の過去に戻ることはできないので。)

④そこで,全ての駆動信号を遅延時間{\tau_i}  (i = 1,2,...N)の最大値{\tau_{max}}だけ遅延したものを駆動信号とします。

{u_i(t)} = u(t+{\tau_i}- {\tau_{max})

これらの信号の遅れ時間は0~{\tau_{max}の範囲になります。ですから図④に示すように,信号の値が定義されているのはt>0となることがわかります。

遅延時間の符号を反転した送信波
④遅延時間が負にならないようにシフト

遅延の実装

信号の遅延をソフトウェアやハードウェアで実現する方法について解説します。

  • 遅延線(Delay Line)
    ハードウェアで実装する場合は,シフトレジスタやメモリを使った遅延線として実装すると考えやすいことがある。
  • 配列のポインタ
    配列に信号データを書き込んでおき,それを読み出すことで遅延を実現できる。MATLABやC/C++言語を使って設計する場合は,配列の添え字(インデックス)やポインタを操作することで,遅延された信号を生成する。

現在のシステム構成では,信号を発生する部分はMATLABを使っています。そこで,これ以降では,配列を用いる方法について説明していきます。

条件は以下のとおりです。

  • 二次元ΔΣ変調後遅延を想定
    フレームのデータは二値に変換され時間的にオーバーサンプルされている。本研究ではサンプリング周波数は3MHzとしている。なお遅延に関しては,データが二値化されていないことを除き,二次元ΔΣ変調後遅延方式と遅延後二次元ΔΣ変調方式とでは同じ処理になる。
  • フレーム長は最大遅延{\tau_{max}}より長いものとする
  • 出力信号のフレームも同じ長さとする
    この遅延処理は,二次元ΔΣ変調で得られたデータを入力とし,出力される信号(駆動信号)は入力と同じ長さのフレーム単位で出力されるものとする。

出力フレームは,入力フレームの配列から,集束のための遅延{\tau_i}に応じて計算されたインデックスでサンプルした値を並べたものになっています。図の①は,出力フレームの最初のデータを作る処理を表しています。入力フレームは2次元配列の形になっています。赤色の点線は入力フレームの最初のインデックス(左端を指しています)から{\tau_i}だけ遅らせたものを表しています(以下,“サンプルライン”と呼ぶことにします)。赤い線の上の黒丸が,値をサンプルする位置を示しています。

①出力フレームの第1サンプル
①出力フレームの第2サンプル

出力フレームの第2サンプルの値は,図②のように,サンプルラインを1つ右にシフトしてサンプルしたデータになります。

③サンプルラインがフレームの端に到達した状態
④サンプルラインが入力フレームからはみ出した状態

以上のようにして第3サンプル,第4サンプル,・・・とサンプルラインをシフトしながら出力フレームの値を求めていくと,サンプルラインが入力フレームの右端に到達した状態になります。1つの入力フレームから計算できるのは,ここまでとなります。

図④は,出力フレームの③の状態よりも後ろのサンプル値を求める場合のサンプルラインを示しています。サンプルラインは入力フレーム(k番目のフレームとしている)をはみ出し,次のk+1番目のフレームにかかっています。

この説明では,フレーム長は指向性形成のための遅延の最大値{\tau_{max}}より大きいことを想定しています。したがって,1つの出力フレームを計算するためには,2つの入力フレームが必要であることになります。

フレーム長が{\tau_{max}}より小さい場合は,3つ以上の入力フレームが必要になります。しかし,ここではフレーム長が{\tau_{max}}より大きい場合について説明します。

数値例

次の条件で遅延量の最大値を計算してみる。

  • スピーカアレイの寸法D:直径1m
  • 収束点のアレイ主軸からの角度の最大値:{\theta_{max}} = 30°
  • 音速 : c = 340m/s

遅延量の最大値の概略値は,

{\tau_{max}} = \frac{{D\sin{\theta_{max}}}}{c}

= \frac{{\sin{\pi/6}}}{340} = \frac{{1/2}}{{340}} = 1.47 \times {10^{ - 3}}

となり,1.47msとなることがわかります。したがって,フレーム長は1.47ms以上となり,出力までは2フレーム分の遅延となるので,最低2.94msのレイテンシの処理になります。

]]>
https://tamlab.fc2.page/category-electronic-work/category-fpga/838/feed/ 0
FPGAによるハードウェア開発 https://tamlab.fc2.page/category-electronic-work/category-fpga/664/ https://tamlab.fc2.page/category-electronic-work/category-fpga/664/#respond Fri, 19 Aug 2022 23:57:35 +0000 https://tamlab.fc2.page/?p=664 FPGAによるハードウェアの開発の手順について解説します。FPGA(Field Programmable Gate Array)は,ユーザが自分で内部の構成をプログラムできる集積回路です。プログラムは,多くの場合,ハードウェア記述言語(HDL)を使って行われます。

<注意を読む>

注意:

この投稿は,未完成のシステムの開発過程の備忘録として書かれたものです。紹介されているコードにはバグや冗長な部分が含まれています。コードやピン配置を参考にする場合は注意をお願いします。また,解説の文章などに誤りや意味不明の箇所が含まれていることにも配慮してください。

<閉じる>

FPGAと開発環境

使用するFPGAと開発環境は,以下のとおりです。

  • FPGA:Lattice Semiconductor社のMachXO2
    評価基板:MachXO2 Breakout Board
  • 開発ソフトウェア:Lattice Diamond
  • その他:ピン配置割り当て作業のサポートにMicrosoft Excelを使う

FPGA

Lattice Semiconductor社 LCMXO2-7000HE (Mach XO2)
 LUT数:6864
 分散SRAMサイズ:54 kb
 EBR SRAMサイズ:240 kb (EBR : Embedded Block RAM)
 EBR SRAMブロック:2 kb×26
 UFMサイズ:256 kb(UFM : User Flash Memory)
 PLL:2個
 データシート:https://www.latticesemi.com/-/media/LatticeSemi/Documents/DataSheets/MachXO23/MachXO2FamilyDataSheetJapaneseLanguageVersion.ashx?document_id=38834

FPGA搭載基板Mach XO2 Breakout Boardを使用
 データシート:https://www.latticesemi.com/-/media/LatticeSemi/Documents/UserManuals/MQ/FPGA-EB-02051-2-3-MachXO2-Breakout-Board-Evaluation-Kit.ashx?document_id=43937

開発ソフトウェア

Lattice Diamond
日本語公式サイト:https://www.latticesemi.com/ja-JP/Products/DesignSoftwareAndIP/FPGAandLDS/LatticeDiamond

日本語ドキュメント:https://www.macnica.co.jp/business/semiconductor/articles/lattice/131999/
株式会社マクニカさんのサイト内のページで,PDFファイルをダウンロードできます。

解説(ダイジェスト版):PDFを開く
「ヤじるし」作成の解説です。印刷して使うことを前提としています。

サポートソフトウェア

合成したハードウェアのFPGAのピン割り当ての作業をサポートするため,自作のMicrosoft Excelファイル(マクロ使用)を利用しています。

MachXO2BB_PinAssign.xlsm:ダウンロード
マクロ付きのExcelファイルです。起動する際,マクロを有効にする必要があります。

使い方の覚え書き:PDFを開く
 備忘録なのでわかりにくいと思います。

ハードウェア設計の手順

デジタルでもアナログでも,電子回路のハードウェア設計にはソフトウェアが使われるようになりました。ソフトウェア化されたハードウェア設計の流れは,図のようになっています。

ハードウェア設計の流れを大雑把にとらえると,次のようになるでしょう。

  • 仕様の検討
  • 回路記述の入力・編集
  • シミュレーション
  • 回路合成

仕様の検討・策定は「どんなものを作るか,よーく考えよう」ということです。この段階では「紙と鉛筆」というアナログツールが主流になると思います。でも,最近は,デジタルツールを使う人もいるのかもしれません。

<注意を読む>

「この段階できちんとしておかないと,設計プロセスの下流の方,例えば実装が終わった段階で仕様変更することになり,納期に間に合わないとか余計なコストが発生するという羽目になります。」

と公式には言っているのですが・・・。私の場合は,仕様も明確にしないまま開発をスタートすることが多いのです。作っているうちに,ああしたらよいこうしたらどうだろうと,仕様を変えながら進めていき,開発の終了と仕様の最終的な決定の時期が同時になるということが多いです。しかも,「開発の終了」といってもそれは常に暫定的なものです。システム開発といっても,売り物を作るのと研究用に作るのでは,やり方が違うということだと思います。

<閉じる>

回路記述の入力・編集から回路合成までの過程でソフトウェアツールが使われます。以下で,解説します。

回路記述の入力

回路図やハードウェア記述言語,ブロック線図などで回路記述を入力し編集します。

HDLによる回路記述

デジタル回路の場合は,ハードウェア記述言語(Hardware Description Language, HDL )を使うことができます。

ハードウェア設計のソフトウェア化」にHDLは大きな役割を果たしています。HDLは特定のIC製造技術に依存しないため,動作検証されたハードウェア記述を異なるメーカのデバイスに対応させるのは容易です。またHDLコードはテキストファイルの形で扱われるため,誰でも回路の仕様を検討することができます。

現在主流になっているHDLは,VHDLとVerilogがあります。本サイトのHDLにはVHDLを使っています。

<続きを読む>

学部の講義科目で,HDLの一つであるVHDLを取り入れていました。VHDLを採用した理由は,そのとき入手できる処理系や技術文書がVHDL関連のものだったからです。Verilogは企業で採用されていることが多いようです。また,高専出身の学生さんも,皆,Verilogを習得していました。

  • VHDLはシステム記述言語のAdaエイダをベースにしている
    文法的には厳密,だそうです。
  • VerilogはC言語がベース
    VHDLより記述が短くなるそうです。C/C++言語を学んでいる方にはとっつきがよいかもしれません。

講義では,VHDLを「ハードウェアを記述する疑似コード」として使いました。講義の到達目標は「コードを読んで理解できる」ところまでです。試験の問題も,コードを示して回路図やタイミングチャートを描かせることしかしていませんでした。

<閉じる>

シミュレーション

実装せずに回路の動作を確認するシミュレーションが,設計の様々な段階で使われます。デジタル回路の設計の場合は,論理的な動作検証のためのシミュレーションと,配置配線の後に,タイミングや消費電力などを考慮した,より詳細なシミュレーションとがあります。

論理シミュレーションは,回路記述の入力・編集に含めて考えてもよいと思います。経験上,回路記述入力の初期段階から動作チェックをしながら作業を進めた方が効率がよいようです。

なお,アナログ回路の分野でも回路動作のシミュレーションのためのSPICE系のソフトウェアがあります。すでにアナログ回路の開発には必須のツールになったと言ってもよいでしょう。

回路合成

シミュレーションがOKとなったら,回路記述から実際の回路を合成する作業が可能になります。この作業は,部品をどこに配置しどう接続するのか,という配置配線の作業が含まれます。昔の回路作製であれば,74シリーズのICなどを基板に配置してリード線とハンダごてで配線する作業になります。

ピン割り当て

FPGAを使ってハードウェア設計をする場合,ピン割り当てpin assignment)の作業が必要となることがあります。回路記述で設定した入出力信号を実装の対象となる(“ターゲット”となる)FPGAのピンに割り当てます。

ピン割り当ては何も指定しなければソフトウェアツールが自動的に行います。入出力信号の総ビット数が少ない場合はソフトウェアツールにおまかせでも問題ないでしょう。しかし,FPGAと他の回路を接続するときなど信号とピンの関係を固定しておきたい場合もあります。また,信号の総ビット数が多くなった場合は,ソフトウェアツールおまかせのピン割り当てでは,関連のある信号がバラバラに配置されてしまい,作業が面倒になります。

そこで,入出力信号とFPGAのピンの対応関係をユーザが明示的に設定するための作業として,ピン割り当てが必要となります。

この作業は,ターゲットとなるFPGAの内部構造に依存します。ユーザが設定した割り当てが物理的に不可能なこともあります。また,ピン割り当てを優先すると,信号の遅延などが当初の仕様を満たさなくなってしまうこともあります。そうなると,回路合成まで戻って最適化のパラメータを設定するなどの作業が必要になります。

基板の設計・作製

一般にFPGAのピン数は非常に多くなります。144ピンとか484ボール(Ball Grid Array, BGAの場合)などです。信号をFPGAの外部と接続するためには,基板に実装する必要があり,基板を設計し,さらにその上にFPGAを実装することは,個人レベルでは大変な作業になります。そこで,FPGAを搭載したプリント基板,例えば秋月電子通商の製品などが市販されています。

さらに,FPGAを基板に実装してあるだけでは不十分で,回路の合成データをFPGA内部や外部のメモリに書き込むための回路も必要となります。

このため,必要最小限の周辺回路も搭載したFPGAの性能評価用の基板(Evaluation BoardやBreakout Boardなどと呼ばれる)が入手可能です。大量生産を目的としない研究室での用途やアマチュアの電子工作レベルでは,これを使うとよいでしょう。

]]>
https://tamlab.fc2.page/category-electronic-work/category-fpga/664/feed/ 0
スピーカアレイ駆動システム VHDLコード (2) (FIFO内蔵版) https://tamlab.fc2.page/category-electronic-work/category-fpga/642/ https://tamlab.fc2.page/category-electronic-work/category-fpga/642/#respond Tue, 16 Aug 2022 23:36:30 +0000 https://tamlab.fc2.page/?p=642 システムの構成

VHDLコード (1) (FIFO無し版)で合成したハードウェアでは,出力データにジッタが見られます。このジッタの原因の一つは,USBによるデータ送信ではデータを受信するタイミングがばらばらで,それをFT232H内蔵のFIFOでは吸収しきれないためと考えました。

そこで,FPGA内部にFIFOを構築したハードウェアを作り,PCからデータを送信する動作確認をしました。

<注意を読む>

注意:

この投稿は,未完成のシステムの開発過程の備忘録として書かれたものです。紹介されているコードにはバグや冗長な部分が含まれています。このコードを参考にする場合は注意をお願いします。また,解説の文章などに誤りや意味不明の箇所が含まれていることにも配慮してください。

<閉じる>

信号発生部分の構成を示します。

アレイスピーカ駆動システム(FIFO内蔵FPGA版)
  • PC
    音声データを2次元ΔΣ変調により1ビット信号群に変換する。このデータをUSBインタフェースを介して送信する。現在,1ビット駆動信号群の生成はMATLABコードにより,データ送信はC++コードで作成したソフトウェアで実行している。
  • USBインタフェース
    データは,FTDI社のUSBインタフェースIC,FT232Hにより受信される。FT232HはAdafruit社の FT232H Breakout基板に搭載されている。
  • FPGA
    FT232Hとのインタフェース,駆動信号のタイミングの調整,データのスピーカへの割り当てなどを実行するハードウェアをFPGA上に構成する。FPGA内部のRAMモジュールを利用してFIFO(First In First Out)バッファを構成している。

動作の概要

試作中のシステムは64個のスピーカをサンプリング周波数3MHzで駆動する。このシステムの動作は以下のようになっている。

  • PCからのデータ送信
    サンプリング周波数3MHz,1回のサンプル当たり64ビットの1ビット信号群を,8ビット単位でUSBインタフェースを介して送信する。
  • FT232Hによる受信
    データはFT232Hで受信され,容量1kByteの内蔵FIFOバッファに入力される。
  • FT232HとFPGAによる受信と出力
    FPGAはFT232HのFIFOバッファから60MHzのクロック信号に同期してデータを読み出す(Synchronous FIFOモード)。読みだされたデータはFPGA内部の容量16kByteのFPGAに書き込まれる。このFIFOは入力幅8bit・出力幅64bitで構成されている。この64bitの出力データは,60MHzのクロック信号を分周して作った3MHzのオーディオ用サンプリングクロックに同期して出力される。

FIFOを内蔵することのメリットは3つあります。

  • 通信のばらつきを吸収することで出力のタイミングのゆらぎ(ジッタ)を無くすことができる
  • タイミングの調整用回路を簡略化できる
    FIFOを使うと,入力と出力のタイミングは同期がとれていなくてもよいので,読み出しタイミングの制御回路が楽になります。
    また,今回採用したFIFOは入力幅と出力幅を変更できるので,それも回路の簡略化につながります。
    FT232H内蔵のFIFOは,FIFOが空でないことを示すRXFフラグしか,FIFOの内部状態を知る手段がありませんでした。これに対し,Lattice DiamondのIPExpressで合成されるFIFOでは,FIFOバッファに設定したデータ数が設定値に達したことを示すAlMostFull信号などを使うことができます。
  • 1回書き込んだデータの再利用もできる
    FPGA内部のFIFOに書き込んだデータは,何度も読み出すことができます。これは,テスト信号を書き込んでおき,何度も送信を繰り返すような用途で便利です。読み出しクロックは100MHz程度まで高くできるようなので,医用超音波帯域の信号の発生にも使えます。

FPGAの構成

 FPGAについて解説します。

  • 使用するFPGA基板
    Lattice社MachXO2 Breakout Board
  • 開発ソフトウェア
    Lattice Diamond
  • ハードウェア記述言語
    VHDL

Lattice Diamondのプロジェクト

プロジェクトのアーカイブ:ダウンロード
解凍せず,そのままLattice Diamondから開くようにしてください。

では,FPGAのハードウェアを記述するVHDLコードについて解説していきます。システムを構築するためには生成したハードウェアの入出力ピンをFPGAのIOピンに割り当てるための作業も必要ですが,それについては別稿で解説する予定です。

VHDLコード

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;
USE ieee.std_logic_unsigned.all;
USE work.ARRAY_PKG.all;	-- 自作のパッケージは“work.”で指定

-- TOP Level Hardware
ENTITY array_speaker_driver IS
	PORT(
			clk_60MHz	: in 		STD_LOGIC;	-- Clock signal from FT232H
			data_in		: in 		STD_LOGIC_VECTOR(N_BYTE*8-1 downto 0);
												--Input from FT232H
			WR			: buffer	STD_LOGIC; 	-- ホストにデータ転送のとき1に
			TXE			: in		STD_LOGIC; 	-- Transmit Bufffer Enough 
												-- 0を確認してバッファに書込み
			RD			: buffer	STD_LOGIC;	-- ホストからデータ転送のとき1に
			RXF			: in		STD_LOGIC;	-- Receiver Buffer Full
												-- 0を確認してバッファから読出し
			OE			: buffer	STD_LOGIC;	-- Output Enable
												-- FIFOからのデータ出力する際1に
			RESET_SW  	: in  		STD_LOGIC; 	-- Connect to tact switch1
			START_SW	: in  		STD_LOGIC; 	-- Connect to tact switch2
			drive		: buffer 	SIG_ARRAY;	-- Output to Loudspeaker Array
			drive_clk	: buffer 	STD_LOGIC; 	-- Clock for loudspeaker array
			LED			: out 		STD_LOGIC_VECTOR(7 downto 0)-- LED Indicator 
	     );
END array_speaker_driver;

ARCHITECTURE behavior OF array_speaker_driver IS
	signal out_data	: SIG_ARRAY;	-- アレイスピーカへの出力
	signal ch_data 	: SIG_ARRAY; 	-- チャンネルセレクタの出力	
	signal FIFO_out	: STD_LOGIC_VECTOR(N_CH*N_BYTE*8-1 downto 0);
	-- タイミング用信号:
	signal d_clk 	: STD_LOGIC; 	-- driver clock 駆動出力の外部同期用
	signal cnt_d	: STD_LOGIC_VECTOR(7 downto 0); -- d_clk用カウンタ
	signal d_clk_D 	: STD_LOGIC;	-- d_clkを60MHzクロックの1周期だけ遅らせたもの
	signal reset 	: STD_LOGIC;	-- システム全体のリセット信号
    signal Ch_cnt	: STD_LOGIC_VECTOR(4 downto 0); --チャンネルの番号を指定
	signal Ch_cnt_D	: STD_LOGIC_VECTOR(4 downto 0); --チャンネルの番号を指定
	signal ch_n		: STD_LOGIC_VECTOR(4 downto 0); --チャンネル数を指定
	signal RD_busy	: STD_LOGIC;	-- Read Busy
	signal RD_busy_D: STD_LOGIC;
	signal OE_busy	: STD_LOGIC; 	-- Output Enable Busy
	signal RXF_in 	: STD_LOGIC;
	-- 動作のスタートなどに関連する信号:
	signal START_D 	: STD_LOGIC := '1';		-- START_SWを1クロック遅延させた信号
	signal START 	: STD_LOGIC := '1'; 	-- スタート状態を表す
	signal RUN 		: STD_LOGIC := '0'; 	-- 実行状態(RUN)を示す
	-- 動作確認用の信号
	signal STATUS	: STD_LOGIC_VECTOR(7 downto 0);
	--CONSTANT ND 	: integer := 19;
	-- FIFO用信号
	signal WrEn 	: STD_LOGIC;
	signal RdEn 	: STD_LOGIC;
	signal RdEn_D 	: STD_LOGIC;
	signal Empty 	: STD_LOGIC;
	signal Full 	: STD_LOGIC;
	signal AmEmpty 	: STD_LOGIC;
	signal AmFull  	: STD_LOGIC;
	signal FIFO_reset  : STD_LOGIC;
	signal RPReset 	   : STD_LOGIC;
	signal AmEmptyThresh : STD_LOGIC_VECTOR(10 downto 0); 
	signal AmFullThresh  : STD_LOGIC_VECTOR(13 downto 0); 
COMPONENT FIFO8to64
    port (
        Data: in  std_logic_vector(7 downto 0); 
        WrClock: in  std_logic; 
        RdClock: in  std_logic; 
        WrEn: in  std_logic; 
        RdEn: in  std_logic; 
        Reset: in  std_logic; 
        RPReset: in  std_logic; 
        AmEmptyThresh: in  std_logic_vector(10 downto 0); 
        AmFullThresh: in  std_logic_vector(13 downto 0); 
        Q: out  std_logic_vector(63 downto 0); 
        Empty: out  std_logic; 
        Full: out  std_logic; 
        AlmostEmpty: out  std_logic; 
        AlmostFull: out  std_logic);
end COMPONENT;
	
BEGIN
drive_clk	&lt;= d_clk;	-- 60MHzを分周して作ったd_clkを外部同期用に出力
reset 	&lt;= RESET_SW; 	-- システムリセット信号をRESET用スイッチにより生成
Ch_n	&lt;= CONV_std_logic_vector(N_CH,5); 	-- N_CHの値をCh_nに設定
AmEmptyThresh &lt;= CONV_std_logic_vector(2048,11); 
AmFullThresh &lt;= CONV_std_logic_vector(2048,14); 

FIFO:FIFO8to64
	PORT MAP(
			Data 		=> data_in,
			WrClock 	=> clk_60MHz,
			RdClock 	=> d_clk,
			WrEn 		=> WrEn,
			RdEn 		=> RdEn,
			Reset 		=> FIFO_reset,
			RPReset 	=> RPReset,
			AmEmptyThresh => AmEmptyThresh,
			AmFullThresh => AmFullThresh,
			Q 			=> FIFO_out,
			Empty 		=> Empty,
			Full 		=> Full,
			AlmostEmpty	=> AmEmpty,
			AlmostFull	=> AmFull
			);
			
WrEn &lt;= RUN AND (NOT RXF) AND NOT(Full) ;	-- RUN=1かつRXF=0のとき1
	-- RXF=0 のときFIFOバッファに読むデータが存在する
RD &lt;= NOT WrEn;
--RdEn &lt;= RUN AND AmEmpty;
RdEn &lt;= RUN AND AmFull;
--RPReset &lt;= '0'; 		-- FIFO_DCのRead Pointerはリセットしない
RPReset &lt;= NOT RESET_SW; 
FIFO_reset &lt;= NOT RESET_SW;-- FIFO_DCのリセット信号
			
-- FT232からの60MHz信号を分周してd_clkを生成
PROCESS(clk_60MHz,reset) -- 60MHzからdrive_clkを作る
CONSTANT WIDTH : integer := 8;	-- '1'の幅
CONSTANT ND : integer := 19;	-- 分周比 
BEGIN
	IF(reset ='0')THEN 
		cnt_d  &lt;= (others => '0'); 
		d_clk &lt;='0';
	ELSIF(clk_60MHz'EVENT AND clk_60MHz='1')THEN
		IF(cnt_d &lt; ND)THEN
			cnt_d &lt;= cnt_d + 1;
			IF(cnt_d &lt; WIDTH)THEN
				d_clk &lt;= '1';
			ELSE
				d_clk &lt;= '0';
			END IF;
		ELSE
			cnt_d  &lt;= (others => '0');
		END IF;
	END IF;
END PROCESS;

PROCESS(d_clk,reset) 
BEGIN
IF(reset = '0')THEN
	out_data &lt;= (others =>(others => '0')); 
	ELSIF(d_clk'EVENT AND d_clk='1')THEN
    for I in 0 to N_CH-1 loop
		out_data(I) &lt;= FIFO_out((I+1)*N_BYTE*8-1 downto I*N_BYTE*8);
	end loop;
	END IF;
END PROCESS;

drive &lt;= out_data;
			
-- クロックに同期したSTART信号を生成

START &lt;= '0' WHEN cnt_d = 0 AND RUN = '1' ELSE
         '1';

-- 外部スイッチで動作信号RUNを生成
PROCESS(clk_60MHz,reset)
BEGIN
	IF(reset='0')THEN
		RUN &lt;= '0';
	ELSIF(clk_60MHz'EVENT AND clk_60MHz = '1')THEN
		IF(START_SW = '0')THEN
			RUN &lt;= '1';
		END IF;
	END IF;
END PROCESS;

-- RD control: FT232HのFIFO出力への制御信号RDを生成
--RD &lt;= NOT (RUN AND (NOT RXF));
	
OE &lt;=  	NOT RUN; 	-- FT232Hの出力許可信号
WR &lt;= '1';			-- FT232HへのWR,とりあえず常に1に
LED &lt;= NOT STATUS;	-- LEDはLowで点灯するので反転

-- STATUS LED表示用の信号
PROCESS(clk_60MHz,reset)
CONSTANT K : integer := 0;
BEGIN
	IF(reset ='0')THEN
		STATUS &lt;= (others =>'0');
		ELSIF(clk_60MHz'EVENT AND clk_60MHz = '1')THEN
			-- 表示が不要な信号はコメントアウト
			STATUS(7) &lt;= drive(K)(7); STATUS(6) &lt;= drive(K)(6);
			STATUS(5) &lt;= drive(K)(5); STATUS(4) &lt;= drive(K)(4);
			STATUS(3) &lt;= drive(K)(3); STATUS(2) &lt;= drive(K)(2);
			STATUS(1) &lt;= drive(K)(1); STATUS(0) &lt;= drive(K)(0);	
			--STATUS &lt;= data_in;
	END IF;  
END PROCESS;

END behavior;

トップレベル回路のEntity宣言

ENTITY array_speaker_driver IS
	PORT(
			clk_60MHz	: in 		STD_LOGIC;	-- Clock signal from FT232H
			data_in		: in 		STD_LOGIC_VECTOR(N_BYTE*8-1 downto 0);
												--Input from FT232H
			WR			: buffer	STD_LOGIC; 	-- ホストにデータ転送のとき1に
			TXE			: in		STD_LOGIC; 	-- Transmit Bufffer Enough 
												-- 0を確認してバッファに書込み
			RD			: buffer	STD_LOGIC;	-- ホストからデータ転送のとき1に
			RXF			: in		STD_LOGIC;	-- Receiver Buffer Full
												-- 0を確認してバッファから読出し
			OE			: buffer	STD_LOGIC;	-- Output Enable
												-- FIFOからのデータ出力する際1に
			RESET_SW  	: in  		STD_LOGIC; 	-- Connect to tact switch1
			START_SW	: in  		STD_LOGIC; 	-- Connect to tact switch2
			drive		: buffer 	SIG_ARRAY;	-- Output to Loudspeaker Array
			drive_clk	: buffer 	STD_LOGIC; 	-- Clock for loudspeaker array
			LED			: out 		STD_LOGIC_VECTOR(7 downto 0)-- LED Indicator 
	     );
END array_speaker_driver;

トップレベル回路の入出力はFIFO無し版と同じです。

Architecture宣言(信号の宣言)

ARCHITECTURE behavior OF array_speaker_driver IS
	signal out_data	: SIG_ARRAY;	-- アレイスピーカへの出力
	signal ch_data 	: SIG_ARRAY; 	-- チャンネルセレクタの出力	
	signal FIFO_out	: STD_LOGIC_VECTOR(N_CH*N_BYTE*8-1 downto 0);
	-- タイミング用信号:
	signal d_clk 	: STD_LOGIC; 	-- driver clock 駆動出力の外部同期用
	signal cnt_d	: STD_LOGIC_VECTOR(7 downto 0); -- d_clk用カウンタ
	signal d_clk_D 	: STD_LOGIC;	-- d_clkを60MHzクロックの1周期だけ遅らせたもの
	signal reset 	: STD_LOGIC;	-- システム全体のリセット信号
    signal Ch_cnt	: STD_LOGIC_VECTOR(4 downto 0); --チャンネルの番号を指定
	signal Ch_cnt_D	: STD_LOGIC_VECTOR(4 downto 0); --チャンネルの番号を指定
	signal ch_n		: STD_LOGIC_VECTOR(4 downto 0); --チャンネル数を指定
	signal RD_busy	: STD_LOGIC;	-- Read Busy
	signal RD_busy_D: STD_LOGIC;
	signal OE_busy	: STD_LOGIC; 	-- Output Enable Busy
	signal RXF_in 	: STD_LOGIC;
	-- 動作のスタートなどに関連する信号:
	signal START_D 	: STD_LOGIC := '1';		-- START_SWを1クロック遅延させた信号
	signal START 	: STD_LOGIC := '1'; 	-- スタート状態を表す
	signal RUN 		: STD_LOGIC := '0'; 	-- 実行状態(RUN)を示す
	-- 動作確認用の信号
	signal STATUS	: STD_LOGIC_VECTOR(7 downto 0);
	--CONSTANT ND 	: integer := 19;
	-- FIFO用信号
	signal WrEn 	: STD_LOGIC;
	signal RdEn 	: STD_LOGIC;
	signal RdEn_D 	: STD_LOGIC;
	signal Empty 	: STD_LOGIC;
	signal Full 	: STD_LOGIC;
	signal AmEmpty 	: STD_LOGIC;
	signal AmFull  	: STD_LOGIC;
	signal FIFO_reset  : STD_LOGIC;
	signal RPReset 	   : STD_LOGIC;
	signal AmEmptyThresh : STD_LOGIC_VECTOR(10 downto 0); 
	signal AmFullThresh  : STD_LOGIC_VECTOR(13 downto 0); 

signal宣言で定義される内部信号には,FIFO関連の信号が追加されています。主要なものだけ解説します。

  • signal FIFO_out : STD_LOGIC_VECTOR(N_CH*N_BYTE*8-1 downto 0);
    内蔵FIFOの出力。
  • signal WrEn : STD_LOGIC;
    内蔵FIFOに入力する書き込み許可信号。1のとき,書き込みクロックの立上りに同期してデータが書き込まれる。
  • signal RdEn : STD_LOGIC;
    内蔵FIFOに入力する読み出し許可信号。1のとき読み出しクロックの立上りに同期してFIFOの内容が出力される。
  • signal Empty : STD_LOGIC;
    内蔵FIFOのEmpty端子に接続されている。FIFOが空になっているとき1となる。
  • signal Full : STD_LOGIC;
    内蔵FIFOのFull端子に接続されている。FIFOが一杯になっているとき1となる。
  • signal AmEmpty : STD_LOGIC;
    内蔵FIFOのAlmostEmpty(“ほとんど空”)信号に接続されている。FIFOに蓄積されているデータの個数がAmEmptyThreshで設定した値以下のとき1になる。
  • signal AmFull : STD_LOGIC;
    内蔵FIFOのAlmostFull(“ほとんど一杯”)信号に接続されている。FIFOに蓄積されているデータの個数がAmFullThreshで設定した値より大きのとき1になる。

Architecture宣言(Component宣言)

COMPONENT FIFO8to64
    port (
        Data: in  std_logic_vector(7 downto 0); 
        WrClock: in  std_logic; 
        RdClock: in  std_logic; 
        WrEn: in  std_logic; 
        RdEn: in  std_logic; 
        Reset: in  std_logic; 
        RPReset: in  std_logic; 
        AmEmptyThresh: in  std_logic_vector(10 downto 0); 
        AmFullThresh: in  std_logic_vector(13 downto 0); 
        Q: out  std_logic_vector(63 downto 0); 
        Empty: out  std_logic; 
        Full: out  std_logic; 
        AlmostEmpty: out  std_logic; 
        AlmostFull: out  std_logic);
end COMPONENT;

Signal宣言の次に,トップレベル回路の中で使用する部品(component)を宣言する。Entity宣言と似ている。

  • COMPONENT FIFO8to64
    部品の名前を定義する。end COMPONENT;までの間にport文を配置する。
  • port ( ・・・
    port宣言。入出力信号の属性を定義する。

この部品,FIFO8to64は,LatticeDiamondのIPExpressで作製された。内部構成はVHDLで記述されているので,ある程度のカスタマイズが可能と考えられる。しかし,コードは2785行にわたり非常に長いので紹介はしない。

Architecture宣言(回路記述の冒頭部分)

BEGIN
drive_clk	&lt;= d_clk;	-- 60MHzを分周して作ったd_clkを外部同期用に出力
reset 	&lt;= RESET_SW; 	-- システムリセット信号をRESET用スイッチにより生成
Ch_n	&lt;= CONV_std_logic_vector(N_CH,5); 	-- N_CHの値をCh_nに設定
AmEmptyThresh &lt;= CONV_std_logic_vector(2048,11); 
AmFullThresh &lt;= CONV_std_logic_vector(2048,14); 
  • AmEmptyThresh <= CONV_std_logic_vector(2048,11);
    11ビット幅の信号AmEmptyThreshを2048という値に設定している。FIFOに蓄積されたデータの個数(8バイトで1つとする)が,この値以下になると,信号AmEmptyは1になる。
  • AmFullThresh <= CONV_std_logic_vector(2048,14);
    14ビット幅の信号AmFullThreshを2048という値に設定している。FIFOに蓄積されたデータの個数(1バイトで1つとする)が,この値以上になると,信号AmFullは1になる。

FIFO8To64のポートマップ

FIFO:FIFO8to64
	PORT MAP(
			Data 		=> data_in,
			WrClock 	=> clk_60MHz,
			RdClock 	=> d_clk,
			WrEn 		=> WrEn,
			RdEn 		=> RdEn,
			Reset 		=> FIFO_reset,
			RPReset 	=> RPReset,
			AmEmptyThresh => AmEmptyThresh,
			AmFullThresh => AmFullThresh,
			Q 			=> FIFO_out,
			Empty 		=> Empty,
			Full 		=> Full,
			AlmostEmpty	=> AmEmpty,
			AlmostFull	=> AmFull
			);

FIFO8To64という部品に内部信号を接続します。これはPortmap文で記述します。

  • FIFO:FIFO8to64
    Portmap文の初めに使う部品とその名前を指示する。FIFO8To64という部品にFIFOという名前を付けて使うことを示す。部品はあらかじめComponent宣言で定義しておく。C言語のプリミティブ宣言に当たる。
  • PORT MAP( ・・・
    Portmap文は,信号を部品の各端子に接続するための記述。記述には2通りの方法がある。一つは,C言語の関数呼び出しのときの実引数のように,PORT MAPの次の括弧の中に,Component宣言で定義した順に信号を配置する方法である。もう1つは,“=>”を使って,端子名と信号名を結合する方法である。後者の方法は,記述が長くなるが,結合リストの順番を変えても良いので信号の数が多いときは楽になる。

制御信号の発生

WrEn &lt;= RUN AND (NOT RXF) AND NOT(Full) ;	-- RUN=1かつRXF=0のとき1
	-- RXF=0 のときFIFOバッファに読むデータが存在する
RD &lt;= NOT WrEn;
--RdEn &lt;= RUN AND AmEmpty;
RdEn &lt;= RUN AND AmFull;
--RPReset &lt;= '0'; 		-- FIFO_DCのRead Pointerはリセットしない
RPReset &lt;= NOT RESET_SW; 
FIFO_reset &lt;= NOT RESET_SW;-- FIFO_DCのリセット信号

制御信号を発生する部分です。FT232Hとのインタフェース用の制御信号も混ざっているので,見づらいコードになっています。

  • WrEn <= RUN AND (NOT RXF) AND NOT(Full) ;
    内蔵FIFOへの書き込み許可信号。RUN状態のときにFT232HのフラグRXFが0,かつ,内蔵FIFOが一杯でなければ1にする。
  • RD <= NOT WrEn;
    FT232Hへの読み出し制御信号RDは,WrEnの否定とする。RD=0のときFT232Hから読み出しをするからである。
  • RdEn <= RUN AND AmFull;
    内蔵FIFOへの読み出し許可信号。RUN状態のときに内蔵FIFOのAmFull信号が1に達したら読み出す。
  • RPReset <= NOT RESET_SW;
    RPResetが1のとき,FIFOの読み出しポインタをリセットする。ブレッドボード上のリセット用タクトスイッチを押したときのみRPResetが1になるようにしている。
  • FIFO_reset <= NOT RESET_SW;
    FIFO全体のリセットもRPResetと同じ信号を入力している。

AmFullは,FIFOに蓄積されたデータ数がAmFullThreshで設定した値以上になると1になる信号です。ですから,内蔵FIFOが空の状態からスタートすると,設定数だけのデータが書き込まれるまでFIFOからの出力は止められることになります。

しかし,読み出し速度が書き込み速度を超えて,FIFOの蓄積データ数が設定数を切ると,出力が途切れることになってしまいます。

以上のように,この制御信号の生成部は未完成で,システムを動かして調整していく必要があります。

分周によりサンプリングクロックを生成

-- FT232からの60MHz信号を分周してd_clkを生成
PROCESS(clk_60MHz,reset) -- 60MHzからdrive_clkを作る
CONSTANT WIDTH : integer := 8;	-- '1'の幅
--CONSTANT ND : integer := 19;	-- 分周比 
BEGIN
	IF(reset ='0')THEN
		cnt_d  &lt;= (others => '0');
		d_clk &lt;='0';
	ELSIF(clk_60MHz'EVENT AND clk_60MHz='1')THEN
		IF(cnt_d &lt; ND)THEN
			cnt_d &lt;= cnt_d + 1;
			IF(cnt_d &lt; WIDTH)THEN
				d_clk &lt;= '1';
			ELSE
				d_clk &lt;= '0';
			END IF;
		ELSE
			cnt_d  &lt;= (others => '0');
		END IF;
	END IF;
END PROCESS;

コメントにあるように,SyncFIFOモードのFT232Hが出力する60MHzのクロック信号を分周して3MHzのクロック信号を生成する部分です。このクロック信号に同期してデータを読み出すことで,スピーカを駆動する信号を発生します

このシステムでは,FPGAに接続したスイッチにより動作の開始やリセットを制御しています。またFT232Hとデータをやり取りするための制御信号を生成します。これらの制御信号の発生回路について説明します。

出力レジスタの記述

PROCESS(d_clk,reset) 
BEGIN
IF(reset = '0')THEN
	out_data &lt;= (others =>(others => '0')); 
	ELSIF(d_clk'EVENT AND d_clk='1')THEN
    for I in 0 to N_CH-1 loop
		out_data(I) &lt;= FIFO_out((I+1)*N_BYTE*8-1 downto I*N_BYTE*8);
	end loop;
	END IF;
END PROCESS;

drive &lt;= out_data;

FIFOの64bit出力FIFO_outを8個の出力レジスタout_data(0)~out_data(7)に接続する回路の記述です。データは

  • ELSIF(d_clk’EVENT AND d_clk=’1′)THEN
    3MHzのクロックd_clkの立上りでデータをレジスタに読み込む。
  • for I in 0 to N_CH-1 loop
    out_data(I) <= FIFO_out((I+1)*N_BYTE*8-1 downto I*N_BYTE*8);
    end loop;
    for loop文となっている。C言語のfor loopとは異なり,「時間的な繰り返しループ」ではなく,「空間的な繰り返し」を記述している。
  • drive <= out_data;
    out_dataはFPGAの出力driveに接続される。

スイッチによる制御信号の発生

-- クロックに同期したSTART信号を生成

START &lt;= '0' WHEN cnt_d = 0 AND RUN = '1' ELSE
         '1';

-- 外部スイッチで動作信号RUNを生成
PROCESS(clk_60MHz,reset)
BEGIN
	IF(reset='0')THEN
		RUN &lt;= '0';
	ELSIF(clk_60MHz'EVENT AND clk_60MHz = '1')THEN
		IF(START_SW = '0')THEN
			RUN &lt;= '1';
		END IF;
	END IF;
END PROCESS;
  • START <= ‘0’ WHEN cnt_d = 0 AND RUN = ‘1’ ELSE ‘1’;
    この信号STARTは,現在,使われていない。動作中であることを示すRUNが1のときに,3MHzの駆動クロックに同期して周期的に0となる信号である。
  • このProcess文は,ブレッドボード上のスタート用タクトスイッチを押すと,RUN=1を生成する。

その他の信号発生回路

OE &lt;=  	NOT RUN; 	-- FT232Hの出力許可信号
WR &lt;= '1';			-- FT232HへのWR,とりあえず常に1に
LED &lt;= NOT STATUS;	-- LEDはLowで点灯するので反転
  • OE <= NOT RUN;
    Output Eneble。FT232Hの出力を許可するアクティブローの制御信号。RUN=1つまり動作状態のとき0にしてFT232Hの出力を許可する。
  • WR <= ‘1’;
    WR(Write)はFPGAがFT232HのFIFOバッファにデータを書き込むときに使うアクティブローの制御信号。今回設計しているシステムではデータはFT232Hから出力されるだけなので,WRは常に1になるようにしている。
  • LED <= NOT STATUS;
    STATUS信号をFPGA基板上の8個のLEDで表示するための記述。MachXO2BB基板上のLEDは接続された信号が‘0’のときに発光する。モニタしたい信号の値が’1’のときにLEDが点灯する方が直感的には望ましいと考えたので,反転して信号LEDに代入している。

STATUSへの内部信号の割り当て

-- STATUS LED表示用の信号
PROCESS(clk_60MHz,reset)
CONSTANT K : integer := 0;
BEGIN
	IF(reset ='0')THEN
		STATUS &lt;= (others =>'0');
		ELSIF(clk_60MHz'EVENT AND clk_60MHz = '1')THEN
			-- 表示が不要な信号はコメントアウト
			--STATUS(7)	&lt;= TXE; 	STATUS(6) &lt;= NOT (WR);
			--STATUS(5)	&lt;= RXF; 	STATUS(4) &lt;= NOT (RD);
			--STATUS(3)	&lt;= OE; 		  
			--STATUS(0)	&lt;= RUN; 
			STATUS(7) &lt;= drive(K)(7); STATUS(6) &lt;= drive(K)(6);
			STATUS(5) &lt;= drive(K)(5); STATUS(4) &lt;= drive(K)(4);
			STATUS(3) &lt;= drive(K)(3); STATUS(2) &lt;= drive(K)(2);
			STATUS(1) &lt;= drive(K)(1); STATUS(0) &lt;= drive(K)(0);	
			--STATUS &lt;= data_in;
	END IF;  

信号STATUSにLEDで表示したい内部信号を割り当てる回路です。ここでは,整数型変数kを指定すると,出力のkチャンネル目のバイトがSTATUSに代入されます。clk_60MHzの立上りに同期して変化するようにしています。単なる代入文だけでも目的は達成するのですが,開発の初期にクロック信号が生成することを確認したかったため,このようにしています。

例えば入力信号の1つをSTATUSの1つのビットに割り当てておきます。その入力端子に取り付けたリード線のもう一方の端子を電源に接触させると対応するLEDが点灯,GNDに接触させたときはLEDは消灯します。リード線の電圧を切り替えてもLEDの点灯状態が変化しない場合はクロックが入力されていないことがわかります。

]]>
https://tamlab.fc2.page/category-electronic-work/category-fpga/642/feed/ 0