ZYBOでシンセサイザー作成(2)オーディオコーデックの使用

今回から徐々にシンセサイザーの作成を始めます。今回は、IICでオーディオコーデック(SSM2603)のレジスタ設定をして、コーデックで音を折り返します。

環境

  • パソコン:Windows10 64 bit
    • Vivado 2020.2 をインストール
    • Xilinx Vitis 2020.2 をインストール
  • ボード:Zybo Z7-20
  • 入力デバイス:パソコンのライン出力 or マイク(ELECOM HS-MC06BK)
  • 出力デバイス:イヤホン or スピーカー

システムの構成

今回作成するシステムの構成が図1です。「AXI GPIO」については省略しています。赤線が音データの経路です。「AXI IIC」を追加して、IIC通信で音を折り返すようにコーデック(SSM2603)を操作します。ADCとDACの電源は切っておきます。今回はDACを使いませんので、MUTEは使えませんが(DACの出力をミュートする機能であるため)、ミュート用のスイッチも次回のために付けておきます。

図1:音を折り返すシステムの構成
図1:音を折り返すシステムの構成

AXI IICとSWの追加(Vivado)

前回作成したLチカとHelloworldの回路に「AXI IIC」と「SW」の回路を追加します。

AXI IICの追加

「Open Block Design」で前回作成したダイアグラムを開きます。ダイアグラムが開いたら、「Add IP」で「AXI IIC」 を追加します(図2)。

図2:AXI IICの追加
図2:AXI IICの追加

追加したら、「Run Connection Automation」をクリックして自動接続をします。「IIC」と「S-AXI」をチェックONします。また「IIC」を選択して、「Select Board Part Interface」を「Custom」にして(図3)、「OK」をクリックします。
自動接続後は、ダイアグラムは図4のようになります。

図3:AXI IIC の自動接続の設定
図3:AXI IIC の自動接続の設定

図4:AXI IICの自動接続後のダイグラム
図4:AXI IICの自動接続後のダイグラム

つづいて、IICの出力ポート名を「iic_rtl」→「iic」に変更しておきます(図5)。
これで「AXI IIC」の接続は完了です。

図5:IICポート名の変更
図5:IICポート名の変更

注意:前回の記事のように「AXI IIC」の「+」部分を展開して、「Make External」をしてしまうと、双方向通信のためのIOBUFがラッパーファイルに追加されないため、自動接続の際にポートも作っています。

ミュート用のスイッチ(SW)追加

スイッチの追加といっても、スイッチの入力ポートとMUTE用の出力ポートをつなぐだけです。まず、ダイアグラムの何もないところで右クリックして、「Create Port...」を選択します(図6)。

図6:「Create Port...」コマンド
図6:「Create Port...」コマンド

スイッチの入力ポートは以下のように設定して(図7)、入力ポートを作成します。

  • Port Name:sw
  • Direction:Input
  • Type:Other
  • Create vector:チェックON   from 0 to 0

図7:「swポートの設定」
図7:「sw」ポートの設定

ミュートの出力ポートは以下のように設定して(図8)、出力ポートを作成します。

  • Port Name:ac_muten
  • Direction:Output
  • Type:Other
  • Create vector:チェックOFF

図8:「ac_muten」ポートの設定
図8:「ac_muten」ポートの設定

ポートが作成できたら、入力ポート「sw[0:0]」と出力ポート「ac_muten」を接続します(図9)。

図9:最終的なダイアグラム
図9:最終的なダイアグラム

ダイアグラムが作り終わったら、「Validate Design」で回路のチェックを行います。つづいて、Sourcesペインで「design_1」を右クリックして、「Create HDL Wrapper...」を選択します。ラッパーを作成するときは、「Copy generated wrapper to allow user edits」を選択します。

制約ファイルの修正

制約ファイル(*.xdc)の修正をします。作成したラッパー(design_1_wrapper.v)をみると、以下のような記述があります。

output [0:0]ac_muten;
inout iic_scl_io;
inout iic_sda_io;
output [3:0]led;
input [0:0]sw;

「led」以外が追加されたポートなので、4つのポートの定義を行います。

swのポート定義は12行目付近にありますので、「sw[0]」の定義のコメントを外します。

##Switches
set_property -dict { PACKAGE_PIN G15   IOSTANDARD LVCMOS33 } [get_ports { sw[0] }]; #IO_L19N_T3_VREF_35 Sch=sw[0]
#set_property -dict { PACKAGE_PIN P15   IOSTANDARD LVCMOS33 } [get_ports { sw[1] }]; #IO_L24P_T3_34 Sch=sw[1]
#set_property -dict { PACKAGE_PIN W13   IOSTANDARD LVCMOS33 } [get_ports { sw[2] }]; #IO_L4N_T0_34 Sch=sw[2]
#set_property -dict { PACKAGE_PIN T16   IOSTANDARD LVCMOS33 } [get_ports { sw[3] }]; #IO_L9P_T1_DQS_34 Sch=sw[3]

オーディオコーデックに関するポートの定義は44行目付近にありますので、「ac_muten」と「ac_scl」と「ac_sda」のコメントを外します。ポート名は「ac_scl」→「iic_scl_io」、「ac_sda」→「iic_sda_io」と変更します。

##Audio Codec
#set_property -dict { PACKAGE_PIN R19   IOSTANDARD LVCMOS33 } [get_ports { ac_bclk }]; #IO_0_34 Sch=ac_bclk
#set_property -dict { PACKAGE_PIN R17   IOSTANDARD LVCMOS33 } [get_ports { ac_mclk }]; #IO_L19N_T3_VREF_34 Sch=ac_mclk
set_property -dict { PACKAGE_PIN P18   IOSTANDARD LVCMOS33 } [get_ports { ac_muten }]; #IO_L23N_T3_34 Sch=ac_muten
#set_property -dict { PACKAGE_PIN R18   IOSTANDARD LVCMOS33 } [get_ports { ac_pbdat }]; #IO_L20N_T3_34 Sch=ac_pbdat
#set_property -dict { PACKAGE_PIN T19   IOSTANDARD LVCMOS33 } [get_ports { ac_pblrc }]; #IO_25_34 Sch=ac_pblrc
#set_property -dict { PACKAGE_PIN R16   IOSTANDARD LVCMOS33 } [get_ports { ac_recdat }]; #IO_L19P_T3_34 Sch=ac_recdat
#set_property -dict { PACKAGE_PIN Y18   IOSTANDARD LVCMOS33 } [get_ports { ac_reclrc }]; #IO_L17P_T2_34 Sch=ac_reclrc
set_property -dict { PACKAGE_PIN N18   IOSTANDARD LVCMOS33 } [get_ports { iic_scl_io }]; #IO_L13P_T2_MRCC_34 Sch=ac_scl
set_property -dict { PACKAGE_PIN N17   IOSTANDARD LVCMOS33 } [get_ports { iic_sda_io }]; #IO_L23P_T3_34 Sch=ac_sda

修正が終わったら、制約ファイルを保存してビットストリームを作成します。ビットストリームが作成できたら、「Export Hardware Platform」でVitisに渡す回路情報を作成します。

IICでコーデックのレジスタ設定(Vitis)

Vitisを起動して、IICでコーデックのレジスタ設定を行います。

プラットフォームプロジェクトの更新

ハードウェアが更新されたので、Vitisにそれを反映させます。Vitisでプラットフォームプロジェクトを選択して、マウス右ボタンから「Update Hardware Specification」を実行します(図10)。プラットフォームファイルを選択して、「OK」をクリックします(図11)。

図10:「Update Hardware Specification」コマンド
図10:「Update Hardware Specification」コマンド

図11:プラットフォームファイルの選択
図11:プラットフォームファイルの選択

あとは、アプリケーションプロジェクトを選択してビルドすれば、プラットフォームプロジェクトも再ビルドされます。

アプリケーションプロジェクトは前回「helloworld」という名前で作りましたが、今回からシンセサイザーをつくっていくので、「zybo_synthesizer」という名前で作りなおしました。テンプレートは前回と同じ「Empty Application」です。

プログラムの作成

プログラム名を「main.c」として、ソースコードを追加します。追加したら、以下のプログラムを記述します。「xiic.h」が定義されてないというエラーが出る場合は、プラットフォームプロジェクトが更新されていないので、更新しておいてください。記述したら、プロジェクトをビルドしてください。

#include "xparameters.h"
#include "xiic.h"
#include "xil_printf.h"
#include <sleep.h>

enum adauRegisterAddresses {
    R0_LEFT_ADC_VOL     = 0x00,
    R1_RIGHT_ADC_VOL    = 0x01,
    R2_LEFT_DAC_VOL     = 0x02,
    R3_RIGHT_DAC_VOL    = 0x03,
    R4_ANALOG_PATH      = 0x04,
    R5_DIGITAL_PATH     = 0x05,
    R6_POWER_MGMT       = 0x06,
    R7_DIGITAL_IF       = 0x07,
    R8_SAMPLE_RATE      = 0x08,
    R9_ACTIVE           = 0x09,
    R15_SOFTWARE_RESET  = 0x0F,
    R16_ALC_CONTROL_1   = 0x10,
    R17_ALC_CONTROL_2   = 0x11,
    R18_ALC_CONTROL_2   = 0x12
};

int CodecWrite(XIic*, u8 Address, u16 data);
XStatus CodecInit(XIic *Iic);

int main(void){
    XIic Iic;
    int status;
    xil_printf("IIC Start\n");

    // Codec Initialization
    status = CodecInit(&Iic);
    if(status != XST_SUCCESS) {
        xil_printf("Error Codec Initialization");
        return XST_FAILURE;
    }
    xil_printf("IIC Finished\n");
    return XST_SUCCESS;
}

// Write Audio Codec Register
int CodecWrite(XIic* Iic, u8 Address, u16 data){
    u8 Device_Address = 0x1A;       // Device ID
    UINTPTR BaseAddress = Iic->BaseAddress; // AXI IIC BaseAddress
    int num;                        // Number of Data sent

    // set write data
    u8 WR_data[2];
    Address = ((Address<<1) & 0xFE);
    WR_data[0] = Address + ((data>>8)&0x01);
    WR_data[1] = (data&0xFF);

    // send data
    num = XIic_Send(BaseAddress, Device_Address, WR_data, 2, XIIC_STOP);
    if(num!=2){
        xil_printf("Writing data Failed\r\n");
        return XST_FAILURE;
    }
    return XST_SUCCESS;
}

// Audio Codec Initialization
XStatus CodecInit(XIic* Iic){
    int status;

    // Initializes XIic instance.
    status = XIic_Initialize(Iic, XPAR_IIC_0_DEVICE_ID);
    if (status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    // Codec register settings
    CodecWrite(Iic, R6_POWER_MGMT,    0x3D); // power on
    CodecWrite(Iic, R0_LEFT_ADC_VOL,  0x97);
    CodecWrite(Iic, R1_RIGHT_ADC_VOL, 0x97);
    CodecWrite(Iic, R2_LEFT_DAC_VOL,  0x79);
    CodecWrite(Iic, R3_RIGHT_DAC_VOL, 0x79);
    CodecWrite(Iic, R4_ANALOG_PATH,   0x2B);
    CodecWrite(Iic, R5_DIGITAL_PATH,  0x08);
    CodecWrite(Iic, R7_DIGITAL_IF,    0x0A);
    CodecWrite(Iic, R8_SAMPLE_RATE,   0x00);
    usleep(80000);  // wait for charging capacity on the VMID pin
    CodecWrite(Iic, R9_ACTIVE,        0x01);  // digital core active
    CodecWrite(Iic, R6_POWER_MGMT,    0x2D);
    return XST_SUCCESS;
}

プログラムの説明

わかりにくいと思われるプログラム箇所について説明していきたいと思います。

レジスタアドレスとレジスタの役割の関連づけ

レジスタの役割がわかりにくいので、列挙型(enum)を使ってレジスタアドレスとレジスタの役割を関連付けています。

enum adauRegisterAddresses {
    R0_LEFT_ADC_VOL     = 0x00,
    R1_RIGHT_ADC_VOL    = 0x01,
    ︙
    R18_ALC_CONTROL_2   = 0x12
};

メイン関数

メイン関数では、大まかに以下のようなことを行っています。

  1. 「IIC Start」をシリアル転送
  2. 「CodecInit」関数を用いて、オーディオコーデックで音を折り返すように設定
  3. 「IIC Finished」をシリアル転送

1と3のシリアル転送についてはプログラムが動いたことをわかりやすくするために、記述しています。

コーデックの設定箇所は以下です。

// Codec Initialization
status = CodecInit(&Iic);
if(status != XST_SUCCESS) {
    xil_printf("Error Codec Initialization");
    return XST_FAILURE;
}

オーディオコーデックを初期化して、失敗したら中断するプログラムとなっています。

コーデックの初期化(CodecInit関数)

「CodecInit」関数では、大まかに以下のようなことを行っています。

  1. XIicインスタンス変数の初期化
  2. コーデックの各レジスタの書き込み

XIicインスタンス変数を初期化している箇所は以下です。

// Initializes XIic instance.
status = XIic_Initialize(Iic, XPAR_IIC_0_DEVICE_ID);
if (status != XST_SUCCESS) {
    return XST_FAILURE;
}

この記述でデバイスIDに基づいて、XIicインスタンス変数の「Iic」に「AXI IIC」の設定やアドレスなどが代入されます。XIic_Initialize 関数は Xilinx によって用意されている API 関数です。

コーデックのレジスタを書き込んでいる箇所は以下です。

 // Codec register settings
    CodecWrite(Iic, R6_POWER_MGMT,    0x3D); // power on
    ︙
    CodecWrite(Iic, R4_ANALOG_PATH,   0x2B);
    ︙
    usleep(80000);  // wait for charging capacity on the VMID pin
    CodecWrite(Iic, R9_ACTIVE,        0x01);  // digital core active
    CodecWrite(Iic, R6_POWER_MGMT,    0x2D);
    return XST_SUCCESS;
}

レジスタの設定はSSM2603のデータシートにおける「CONTROL REGISTER SEQUENCING」の手順どおりに行っています。 その手順が以下です。

  1. R6レジスタを書き込んで、「Out」ビットを除いて、使用するものに電源を入れる
  2. 設定が必要なレジスタに書き込む
  3. カップリングコンデンサに電源が供給されるまで待つ
  4. R9レジスタの「active」ビットとR6レジスタの「Out」ビットをセットする

1に関する記述箇所は以下です。

CodecWrite(Iic, R6_POWER_MGMT,    0x3D); // power on

モードを「Sidetone 」として使うので、R6レジスタの「MIC」、「CLKOUT」、「PWROFF」ビットを「0」にして電源ONします。

2に関する記述箇所は以下です。

CodecWrite(Iic, R4_ANALOG_PATH,   0x2B);

R4レジスタで「SIDETONE_EN」と「MICBOOST」をデフォルト値から変更しています。 それぞれのビットの説明は以下です。

  • SIDETONE_EN:「1」でサイドトーンを可能にさせる
  • MICBOOST:「1」でマイクのゲインを20dB上げる

「MICBOOST」を使わないと、音の折り返しがあるかわかりづらいので、「MICBOOST」をセットしました。 また、他のレジスタも書き込んでいますが、デフォルト値から変更していません。次回、音を再生するために書いておきました。

3に関する記述箇所は以下です。

usleep(80000);  // wait for charging capacity on the VMID pin

usleep関数を使って、80000 us 待機しています。

データシートによると

\displaystyle{
t = C \times 25000 / 3.5
}

だけ待機すればよいということでした。 CはVMINピンとGNDの間にあるコンデンサの容量です。ZYBO Z7-20の回路図を見ると、100nFと10uFのコンデンサがありますので、72142us だけ待てばよいということになります。念のために80000 us (80 ms) 待っています。

4に関する記述箇所は以下です。

CodecWrite(Iic, R9_ACTIVE,        0x01);  // digital core active
CodecWrite(Iic, R6_POWER_MGMT,    0x2D);

R9レジスタに0x01を書き込んで、R6レジスタを0x3D→0x2Dに変更することでオーディオコーデックを動作させています。

コーデックのレジスタの書き込み(CodecWrite関数)

レジスタにデータを書き込むには図12のようにしてデータを送信します。

図12:書き込みシーケンス
図12:書き込みシーケンス(SSM2608のデータシートから引用)

  • S/P = START/STOP BIT.
  • A0 = I2C R/W BIT.
  • A(S) = ACKNOWLEDGE BY SLAVE.

DEVICE ADDRESSの設定は以下で記述しています。

u8 Device_Address = 0x1A;       // Device ID

SSM2603のDEVICE ADDRESSは「CSB」ピンが「L」ならば0x1A (0b0011010)、「H」ならば0x1B (0b0011011)です。ZYBO z7の回路図を見ると、CSBピンはGNDに接続されていますので、0x1Aとしています。

[B0-B15]のRESISTER ADDRESSとRESISTER DATAの設定は以下で記述しています。

// set write date
u8 WR_data[2];
Address = ((Address<<1) & 0xFE);
WR_data[0] = Address + ((data>>8)&0x01);
WR_data[1] = (data&0xFF);

シフトとマスクを使って、アドレスとデータを形式にあうようにセットしています。

IICを使用した送信は以下の記述で行っています。

// send data
num = XIic_Send(BaseAddress, Device_Address, WR_data, 2, XIIC_STOP);

XIic_Send 関数は Xilinx によって用意されている API 関数です。

各引数は以下のようになっています。

  1. UINTPTR BaseAddress:AXI IICのベースアドレス
  2. u8 Address:デバイスID
  3. u8 * BufferPtr:送信するデータのアドレス
  4. unsigned ByteCount:送信するデータの数
  5. u8 Option:送信後の動作

プログラムの実行

プログラムの実行前に以下のことを行います。

  • ZyboのMIC INにマイクまたはPCのライン出力を接続
  • ZyboのHPH OUTにスピーカーまたはイヤホンを接続
  • ZyboとPCをUSBで接続
  • シリアルポートを接続(面倒くさい場合はしなくてもいいです)

MIC INにPCのライン出力を接続する場合は、MICBOOSTで音量がかなり大きくなるのでPCの音量を下げておいてください。自分は爆音でビビりました。

前回は「Debug」で回路構築してから、「resume」でプログラムを実行しましたが、「Run」をクリックすることで回路構築とプログラムの実行が同時に行うことができます(図13)。

図13:「Run」でプログラムの実行
図13:「Run」でプログラムの実行

マイクをつないでいる場合は、声を出したりするとスピーカーから音声が返ってきます(図14)。また、PCのライン出力につないだ場合も、Youtubeなどを再生するとスピーカーから音が返ってきます。

図14:音声を折り返している様子
図14:音声を折り返している様子

おわりに

今回で、オーディオコーデックを使うことができました。次回はBRAMを使用して正弦波合成を行い、シミュレーションで確認しようと思います。

参考文献

ZYBOでシンセサイザー作成(1)LチカとHelloWorld

ZYBOを使ってシンセサイザーを作成したいと思います。久しぶりにVivadoを使うので、まずはLチカとHello Worldからやってみます。主に今回の記事は以下の参考文献をもとに書きました。

環境

  • パソコン:Windows10 64 bit
    • Vivado 2020.2 をインストール
    • Xilinx Vitis 2020.2 をインストール
  • ボード:ZYBO Z7-20

Board ファイルのインストール

初期状態ではDigilentのボードであるZYBO-z7-20のボードファイルがないため、ボードファイルをインストールします。DigilentのボードファイルのダウンロードはInstalling Digilent Board Filesの「archive」をクリックすることでダウンロードすることができます。そのzipファイルを解凍して、そのフォルダの中の

「vivado-boards-master\new\board_files\zybo-z7-20」

のフォルダを

「C:\Xilinx\Vivado\<version>\data\boards\board_files」

の下に貼り付けすることでインストールできます。

プロジェクトの作成

まず、「Create New Project」をクリックして、プロジェクトを新規に作成します。

プロジェクトの作成画面1

「Create a New Vivado Project」はプロジェクトを作成するためにやることが書いてあるだけなので、そのまま「Next」をクリックします。

プロジェクトの作成画面2

「Project Name」では、プロジェクトの名前とプロジェクトの格納フォルダを指定します。私の場合は、

  • プロジェクト名:zybo_synthesizer
  • プロジェクトの格納フォルダ:D:/v20

として、「Create project subdirectory」にチェックしました。「Create project subdirectiory」にチェックONすることで、プロジェクトの格納フォルダの下にプロジェクト名のフォルダが作成され、その下にプロジェクトファイルが作成されます。

プロジェクトの作成画面3

「Project type」では、作成するプロジェクトのタイプを指定します。ここでは「RTL Project」を選択します。「Do not specify sources at this time」にもチェックONしておきます。チェックONしておくと、verilogファイルなどのソースファイルを追加する画面を省略します。あとで追加することもできるので基本的にチェックONしておきます。

プロジェクトの作成画面4

「Default Part」では、使用するFPGAバイスを選択します。ボードファイルのインストールをしていれば、「Boards」を選択することで、「Display Name」の中に「Zybo Z7-20」があると思います。「Zybo Z7-20」を選択して、「Next」をクリックします。

プロジェクトの作成画面5

「New Project Summary」では、プロジェクトの設定内容を確認して、問題なければ「Finish」をクリックします。

プロジェクトの作成画面6

回路の作成

Verilogファイルは書かないで、VivadoのIPインテグレータだけを使って、回路の作成をします。

IP インテグレータによる回路作成

IP インテグレータの起動

Flow Navigator から 「IP INTEGRATOR」の「Create Block Design」 をクリックします。 小さいウィンドウが出て、「Design Name」 や 「Directory」を変えることができますが、デフォルトのままでいいです。これで「Diagram」のタブができます。「Diagram」で GUI 的に回路作成ができます。

インテグレータによる回路作成1

IP の配置

Diagramで「ADD IP」をクリックすると、IP(機能ごとにまとまった回路)のリストが表示されますので、「ZYNQ7_Processing_System」をダブルクリックして配置します。また、同じようにして「AXI GPIO」も配置します。

インテグレータによる回路作成2

IP の接続

IPを配置したら、「Run Block Automation」をクリックします。Zynq の設定が表示されますので、「Apply Board Preset」にチェックがあることを確認して、「OK」をクリックします。「Apply Board Preset」にチェックONすることで、ボード上にあるDDRメモリなどにしたがって Zynq の設定をしてくれます。

インテグレータによる回路作成3

「AXI GPIO」をダブルクリックして、「AXI GPIO」の設定をします。「IP Configuration」タブで「All Outputs」にチェックをして、「GPIO Width」を 4 と設定します。「OK」をクリックして設定を終了します。

インテグレータによる回路作成4

「Run Connection Automation」をクリックして、Zynq と「AXI GPIO」を自動接続をします。接続の設定では、「GPIO」はチェックOFFし、「S-AXI」にはチェックONして、「OK」をクリックします。

インテグレータによる回路作成5
インテグレータによる回路作成6

「Run Connection Automation」を実行すると、「Processor System Reset」(リセット信号を生成する回路)と「AXI Interconnect」(AXI のハブ的な回路)が自動生成されて、IP間が接続されます。

インテグレータによる回路作成7

つづいて、「AXI GPIO」の「GPIO」端子の「+」部分をクリックすると、端子が展開されて「gpio_io_o[3:0]」が表示されます。これを右クリックして、メニューから「Make External」を実行することで、「AXI GPIO」の出力とFPGAの出力ポートを接続します。

インテグレータによる回路作成8

これで回路ができました。

制約ファイル(*.xdc)の読み込みと修正

制約ファイル(*.xdc)のダウンロード

FPGA端子の設定をする制約ファイルをダウンロードします。ZYBO Z7用の xdc ファイルは、Digilentページの「Documentaion」→「Master XDC Files」をクリックすると、Digilentの各ボードの制約ファイルがあるGItHubにとぶので、そこからZybo-Z7-Master.xdcをダウンロードします(私は一つのファイルだけダウンロードすることはできなかったので、他のボードの制約ファイルと一緒にダウンロードしました)。

制約ファイル(*.xdc)の読み込み

制約ファイルをプロジェクトに読み込むには、「Project MANAGER」の「Add Sources」をクリックし、「Add or create constraints」を選択します。続いて、さきほどダウンロードしたZybo-Z7-Master.xdcをプロジェクトファイル(*.xpr)と同じ階層にコピーして、それを読み込みます。「Copy constraints files into project」はチェックOFFにしておきます。チェックONにすることで、プロジェクトフォルダのどこかに制約ファイルがコピーされて置かれますが、深い階層に置かれてしまって、制約ファイルの修正や確認がしづらくなるので、私はこのようにしています。「Finish」で制約ファイルが読み込まれます。

制約ファイルの修正

次に、制約ファイルの修正をします。今回はLEDを使いたいので、LED設定箇所の27行目から30行目のコメントを以下のように外します。

##LEDs
set_property -dict { PACKAGE_PIN M14   IOSTANDARD LVCMOS33 } [get_ports { led[0] }]; #IO_L23P_T3_35 Sch=led[0]
set_property -dict { PACKAGE_PIN M15   IOSTANDARD LVCMOS33 } [get_ports { led[1] }]; #IO_L23N_T3_35 Sch=led[1]
set_property -dict { PACKAGE_PIN G14   IOSTANDARD LVCMOS33 } [get_ports { led[2] }]; #IO_0_35 Sch=led[2]
set_property -dict { PACKAGE_PIN D18   IOSTANDARD LVCMOS33 } [get_ports { led[3] }]; #IO_L3N_T0_DQS_AD1N_35 Sch=led[3]

ポート名の変更

制約ファイル上でポート名が「led」になっているので、IPインテグレータでもポート名を「led」にします。「gpio_io_o_0」を選択して、「External Port Properties」ペインで「Name」を「led」に変更します。

ポート名を変更

上位階層とビットストリームの作成

ダイアグラムのチェック

ダイアグラムが完成したら、右クリックのメニューから「Validate Design」を実行して、整合性のチェックをします。回路に問題がなければ、「Validation successful.」を表示します。

Validation Successful

もしかしたら、以下のようなクリティカルワーニングが発生するかもしれませんが、Digilentのボードファイルによる問題なので無視しても大丈夫です。

CRITICAL WARNING: [PSU-1]  Parameter : PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_0 has negative value -0.050 . PS DDR interfaces might fail when entering negative DQS skew values. 
CRITICAL WARNING: [PSU-2]  Parameter : PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_1 has negative value -0.044 . PS DDR interfaces might fail when entering negative DQS skew values. 
CRITICAL WARNING: [PSU-3]  Parameter : PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_2 has negative value -0.035 . PS DDR interfaces might fail when entering negative DQS skew values. 
CRITICAL WARNING: [PSU-4]  Parameter : PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_3 has negative value -0.100 . PS DDR interfaces might fail when entering negative DQS skew values. 

上位階層の作成

Sourcesペインにおいて、ダイアグラムで作成した階層「design_1」を選択し、右クリックから「Create HDL Wrapper...」を実行します。「Create HDL Wrapper...」によって、ダイアグラムで作った回路を使用するVerilogファイルが作成されます。「Options」では、「Copy generated wrapper to allow user edits 」を選択します。「Let Vivado manage wrapper and auto-update」を選択すると、自動的にラッパーが更新されるそうですが、経験的にポートが追加されないことがあったので、「Copy generated wrapper to allow user edits 」を選択します。

ビットストリームの作成

「PROGRAM AND DEBUG」の「Generate Bitstream」をクリックして、ビットストリームを作成します。論理合成と配置・配線をしていないので、「No Implementation Results Available」のダイアログが出ますが、「Yes」をクリックします。「Launch Runs」では、そのまま「OK」をクリックします。Number of jobsで使用するスレッドの数を指定できますが、デフォルトのままでいいと思います。

正常終了すると、右上のステータスが「write bitstream Complete」となり、次の処理を確認するダイアログが表示されます。「Open Implemented Design」を選択すると、回路規模などを確認することができます。普段、私は「Open Implemented Design」で回路規模を確認することが多いです。回路規模に興味がない人は「Cancel」をクリックで大丈夫です。

Bitstream_Generation_Completed

Zynq のプログラムを実行

Vitisを使って、Zynqのプログラムを実行していきます。

回路情報のコピーとVitis の起動

Vitisを起動する前にVivadoからVitisに対して回路情報を渡す必要があります。そのため、Vivadoでその作業をしてから、Vitis を起動します。

回路情報のコピー

メニューバーから「File」→「Export」→「Export Hardware...」を実行します。最初の画面では、「Next」をクリックします。

エクスポート画面1

「Output」では「Include bitstream」を選択します。これで、Vitisに回路情報を渡すことができます。

エクスポート画面2

「Files」では以下のように設定しました。

  • 名称:design_1_wrapper(デフォルト設定)
  • 保存先:D:/v20/zybo_synthesizer/zybo_synthesizer.vitis
エクスポート画面3

最後に「Finish」をクリックしてエクスポートします。

エクスポート画面4

Vitisの起動

メニューバーから「Tools」→「Launch Vitis IDE」でVitisを起動します。

Zynqのプログラム実行1

ワークスペースの場所は先ほど作成したzybo_synthesizer.vitisフォルダを指定し、「Launch」をクリックします。

Zynqのプログラム実行2

各種プロジェクトの作成

Vitisでは「プラットフォームプロジェクト」と「アプリケーションプロジェクト」の作成をする必要がありますので、これらのプロジェクトを作成していきます。

プラットフォームプロジェクトの作成

起動画面から「Create Platform Project」をクリックします。

Zynqのプログラム実行3

最初の画面では、プロジェクト名を入力します。プロジェクト名は「zybo_synthesizer_platform」としました。

Zynqのプログラム実行4

つぎは「Create a new platform from hardware (XSA)」タブで、Vivadoで作成したプラットフォームファイル(*.xsa)を指定します。「Finish」でプロジェクトが作成されます。

Zynqのプログラム実行5

アプリケーションプロジェクトの作成

メニューバーから「File」→「New」→「Application Project...」を実行します。

Zynqのプログラム実行6

最初の画面では、そのまま「Next」をクリックします。

Zynqのプログラム実行7

次の画面では、プラットフォームの選択をします。デフォルトでさきほど作成したプラットフォームが設定されていますので、確認してそのまま「Next」をクリックします。

Zynqのプログラム実行8

つづいて、アプリケーションプロジェクト名を入力する画面になります。アプリケーションプロジェクト名は「helloworld」としました。

Zynqのプログラム実行9

ドメインの選択では、そのまま「Next」をクリックします。

Zynqのプログラム実行10

テンプレートを選択する画面では、「Empty Application」を選択して、「Finish」をクリックします。

Zynqのプログラム実行11

プログラムの作成と実行

プログラムをC言語で作成して、シリアルターミナルなどを設定して、プログラムを実行します。

Cファイルの作成

アプリケーションプロジェクト内の「src」フォルダを右クリックして、「New」→「File」をクリックします。ファイル名を入力する画面では、ファイル名を「helloworld.c」として、「Finish」をクリックします。

Zynqのプログラム実行12
Zynqのプログラム実行13

プログラムの記述

プログラムはAPIを用いて記述します。「helloworld.c」に以下のプログラムを記述します。一応、プログラムが記述された「helloworld.c」はGitHubの「1st」フォルダに置いてあります(2021/5/12 追記)。

#include "xparameters.h" // 各IPのパラメータがあるファイル
#include "xgpio.h"       // AXI GPIO用のAPIがあるファイル
#include "xil_printf.h"  // xil_printfを使うためのファイル

int main(void){
    int status;
    XGpio Gpio;

    // Hello World をシリアル転送
    xil_printf("Hello World. \n");

    /* GPIO の初期化 */
    // Gpio変数の初期設定
    status = XGpio_Initialize(&Gpio, XPAR_GPIO_0_DEVICE_ID);
    if(status != XST_SUCCESS) return XST_FAILURE;

    // 入出力の設定
    // 2番目の引数:設定するインターフェース  1:GPIO 2:GPIO2
    // 3番目の引数:各ポートの入出力の設定  0:出力  1:入力
    XGpio_SetDataDirection(&Gpio, 1, 0x0); // 全て出力に設定

    // 信号レベルの設定
    // 2番目の引数:設定するインターフェース  1:GPIO 2:GPIO2
    // 3番目の引数:各ポートの出力レベルの設定   0:Low  1:High
    XGpio_DiscreteWrite(&Gpio, 1, 0xF); // 全てHighに設定
}

ビルド

アプリケーションプロジェクトの「helloworld」を選択して、メニューバーから「Project」→「Build Project」を実行して、プロジェクトのビルドを行います。

プロジェクトのビルド

PCとボードの接続

ZYBOのボード上のジャンパーピンを以下のように設定します。

ジャンパーピンの設定

こうすることで以下のように設定されます。

  • 電源供給方法:USBから供給
  • 回路情報やプログラムの実行方法:USB経由でPCから実施

この状態でPCとZYBOをMicro-USBで接続すると、赤色のLEDが点灯します。

デバッグの設定

デバッグ設定は、アプリケーションプロジェクト「helloworld」を選択して、虫マークのプルダウンメニューから「Debug As」→「Lunch on Hardware (Single Application Debug)」で実行する。

デバッグ設定

実行すると、FPGAに回路情報を渡して、デバッグ時の操作画面に変更されます。

FPGAプログラム中

シリアルターミナルの設定

シリアルターミナルの設定は、「Vitis Serial Terminal」タブの「+」をクリックし、COMポートと通信速度を設定して「OK」をクリックします。基本的には通信速度はデフォルトで、ポートは選択可能なものを選べばいいと思います。

シリアルターミナル
シリアルターミナルの設定

プログラムの実行

プログラムの実行は「Resume」ボタンをクリックすることで行われます。

resumeボタン

実行すると、シリアルターミナルには「Hello World」と表示されます。

シリアルターミナルの様子

また、4つのLEDが以下のように点灯します。

ZYBOがLEDを点灯している様子

おわりに

ZYBOでLチカとHello Worldをしました。まだ、Vitisに慣れないので、慣れていきたいと思います。

マイクロマウス(旧ハーフサイズ)始めました

マイクロマウス(旧:ハーフサイズ)を始めました。学部生のころ、マイクロマウスを製作するサークルに入っていたのですが、全くわからなくて挫折してしまいました。ただ、いまならできるかなと思ったのでマイクロマウスを始めました。とりあえずの目標は4 x 4迷路の走破です。現在、設計までは終えていて、機械部品と基板を発注しているところです。ちなみに私は機械系ではなく情報系の人間なので、優しい目で見ていただければ幸いです。

コンセプト

コンセプトは「作りやすいマウス」です。このコンセプトのため、全日本マイクロマウス大会のテクニカルデータを見て、IC やモータなどのキーパーツについては一般に使われているものを選びました。また、オシロスコープがないため、電気的トラブルがないようにはんだ付けしやすい IC を選びました。あとは自分で加工する必要がないように機体を設計しました。

機体情報

マイクロマウスの外観
機体名 wakaba
サイズ(長さ x 幅 x 高さ) 60.0mm x 45.0mm x 14.8mm
マイコン RX631 (48pin)
モーター Didel MK06-4.5
モータードライバー DRV8836
壁センサ OSI5FU3A11C & LTR-4206E
ジャイロセンサ MPU6000
エンコーダ AS5047P
ギア比 4.44 (40 : 9)
タイヤ径 14.8mm

機械設計にはFusion 360を使いました。機体は2輪となっています。 センサーホルダーを作って、センサーを固定するつもりです。 また、磁気式エンコーダを使えるようにモーターマウントや磁石マウントを設計しました。 加えて、ホイールはギア付きホイールとなっています。調べてもモジュール0.3で穴径が大きいギアは見つからなかったので、ブログでよく見られたギア付きホイールとしました。センサーホルダー、ホイール、モーターマウント、磁石マウントはDMM.makeに発注しました。素材はアクリル(Ultra Mode)です。

回路図

マイクロマウスの回路図

回路図PDF版

回路についてはサークルの先輩の回路やブログで見られた回路、マウス本の回路を参考にして設計しました。基本的にはコンセプト通りに半田付けしやすい足があるIC を選びました。ただ、モータードライバーのDRV8836とジャイロセンサーのMPU6000は足がないため頑張るしかないです。半田付けを久しぶりにやるので、不安で仕方がありません。

配線図

マイクロマウスの配線図

配線図PDF版(縮尺:2.5倍)

ベタGNDをする前の配線となっています。基本的には0.1524mm幅で配線しました。赤外線LEDやスピーカー、モータードライバーにおける配線幅については0.3048mmや0.4064mmを使用しました。羽のように繋がっている基板は磁気式エンコーダ用の基板です。あとでニッパーでカットして、真ん中の溝に嵌めるつもりです。また、線幅を変え忘れたので見にくいと思いますが、センサーホルダーを差し込むための溝もあります。基板についてはelecrowに発注しました。

おわりに

4月から就職なので、時間がなくなりそうですが少しずつ進めていきたいと思います。また、elecrowの注文でクレジットカード情報を入力していなかったため、面倒くさいことになっているので解決したいと思います。

追記(2021/9/15)

回路基板が届いたので、全ての部品をハンダ付けしましたが、プログラムの書き込みができませんでした。その際、基板の問題かと思い、回路基板を作りなおしています。それに関する詳細の記事が以下です。

また、作り直した基板でプログラムの書き込みは上手くいったのですが、AD変換が上手くいかなかったので、基板を更に作りなおしています。それに関する詳細の記事が以下です。

Cambridge Music Technologyでマルチトラックの音源を提供しているというハナシ

はじめに

機械学習などをやっていると困るのはデータを集めることだと思います。私も音源分離の機械学習をやったことがあるのですが、データを集めるのに苦労しました。音楽データの音源分離をする場合は、データセットMUSDB18が一番有名ではないかと思います。MUSDB18は150曲(約10時間)の音楽トラックデータセットで、曲ごとにボーカル、ベース、ドラム、その他の楽器に分けられたステムデータがあります。試しにモデルを動かしてみたいときはMUSDB18だけで十分だと思いますが、もっとモデルを学習させたいときがあるかと思います。

Cambridge Music Technology

Cambridge Music Techonologyではミキシングの練習をサポートするために無料でダウンロードできるマルチトラックを提供しています。

www.cambridge-mt.com

今見たところ471曲あるそうです。ボーカルが入っていない曲もありますので、全てのデータを学習に使うことはできませんが、学習データをかなり多く増やすことができます。学習の際の注意点としては、ステムデータではなくパラデータなので、学習前に自分でステムデータにする必要がある点です。そこは、自分で頑張りましょう。

ブログに掲載することに関して

今後、このブログでは音楽データに対する処理結果を載せたいと思うのですが、なかなか掲載できる音楽データは見つかりません。そこで、Cambridge Music Techhologyのサイト運営者の方に「信号処理技術を紹介するためにCambridge Music Techologyのデータで処理した結果をブログにアップロードできないか」と質問してみました。返信としては、「教育目的に該当するので掲載しても大丈夫です」とのことでした。また、「アーティストとCambridge Music Technologyのクレジットを付けてください」とのことでした。返信がきたときは、かなり嬉しかったです。本当にありがとうございます。

音源分離や自動ミキシングなどの処理結果を掲載する際は、音楽データを使わせていただきたいと思います。

Dockerの始め方(Python環境の共有)

Dockerロゴ
画像:Dockerロゴ

PythonGUIアプリを作成して、それを他の人に動作確認または修正をしてもらいたいときがあります。このとき、GUIアプリを動かすのにPythonパッケージが多数必要であったり、OSによってGUIアプリの動作が違っていたりして動作確認が難しいことがありました。

Dockerを使えば、その問題を解決できるということで、Dockerを導入したのですが、最初の一歩がわからず、環境を共有してもらうのに2、3日かかってしまいました。それなので、Dockerを使って環境を共有するまでのやり方をまとめました。Dockerのインストール方法やDockerについての説明は省いています。初学者の備忘録ですが、参考にしてもらえれば幸いです。

主に参考にした資料は以下です。

環境

  • 開発PC:Windows10 64bit
    • WSL2を導入
    • Ubuntu 20.04を導入
    • Docker Desktop for windowsを導入

構築する環境

コンテナの作成

まず、コンテナの作成をします。コンテナの作成は「docker container run <オプション><イメージ名><コンテナで実行するコマンド>」でできます。 Ubuntu20.04を動作させるコンテナの作成は以下のコマンドでできます。

$ docker container run ubuntu:20.04

イメージが手元にない場合はイメージをDockerHubからダウンロードしてきます。<オプション>と<コンテナで実行するコマンド>は上のように省略可能です。 <コンテナで実行するコマンド>を省略した場合、イメージに設定されたデフォルトのコマンドが実行されます。

ただ、上のコマンドではコンテナに入ることもできませんし、すぐにコンテナが停止してしまうのでオプションをつける必要があります。 コンテナのbash操作は以下のコマンドでできます。

$ docker container run -it ubuntu:20.04 bash

「-it」というオプションをつけることでコンテナの直接操作ができるようになります。 ちなみに「-i, --interactive」と「-t, --tty」をくっつけたオプションとなっています。

コンテナの中の環境構築

コンテナのbash操作が可能になればコンテナの中のrootディレクトリにいると思います。 コンテナの中の環境構築ではDocker特有のことはあまりないです。コンテナに入った直後にroot権限をもつので sudoコマンドなしでパッケージをインストールできます。

自分の場合ですと、インストールを早くするためにapt-getの利用リポジトリを日本サーバーに変更します(wxPythonを使うためのパッケージのダウンロードが結構かかる)。それから、Python 3.8とpipをインストールします。

# apt-getの利用リポジトリを日本サーバーに変更
$ sed -i.bak -e "s%http://archive.ubuntu.com/ubuntu/%http://ftp.jaist.ac.jp/pub/Linux/ubuntu/%g" /etc/apt/sources.list

# python3.8 と pipのインストール
$ apt update
$ apt install -y python3.8 python3-pip

wxPythonに必要なライブラリをインストールする。

# wxPythonに必要なライブラリを入れる
$ apt install -y libgtk-3-dev \
libnotify-dev \
libsdl2-2.0-0 \
libsdl2-dev

必要なPythonのパッケージをインストールする。

# Pythonのパッケージを入れる
$ pip3 install numpy 
$ pip3 install scipy
$ pip3 install matplotlib
$ pip3 install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-20.04 wxPython

コンテナにソースをコピー

環境構築ができたら、ホストからコンテナにソースコードをコピーします。 コンテナへのファイルのコピーは「docker container cp <コピー元><コピー先>」でできます。 今利用しているコンソールはコンテナのbashを表示していますので、別のコンソールを起動してコマンドを入力してください。 ただ、コンテナの名前がわからないと思いますので、「docker container ls 」 で現在稼働しているコンテナを表示させて名前を調べてください。コンテナのパスは「コンテナ名:コンテナ内のパス」 という形式で指定してください。

# コンテナの名前を調べる
$ docker container ls
CONTAINER ID   IMAGE          COMMAND   CREATED       STATUS       PORTS     NAMES  
c68db22ff87d   ubuntu:20.04   "bash"    2 hours ago   Up 2 hours            pedantic_williams  
# ソースをディレクトリごとコピー
$ docker container cp  src  pedantic_williams:/

今回は例として、srcに以下のwx_example.pyだけを入れています。

import wx

class MyApp(wx.Frame):
    def __init__(self, parent, id = -1, title = None):
        wx.Frame.__init__(self, parent, id, title)

        self.SetSize((300, 300))

        #パネルの作成
        panel_G = wx.Panel(self, id, pos=(10, 10), size=(200, 280))
        panel_G.SetBackgroundColour((0, 255, 0))

        #描画
        self.Show(True)

if __name__ == "__main__":
    app = wx.App()
    MyApp(None, wx.ID_ANY, "title")
    app.MainLoop()

GUIアプリの起動

コンテナの中でコードを実行する前に、ウィンドウをホスト側にとばすためにコンテナのDISPLAY環境変数を設定する必要があります。別のコンソールで「echo $DISPLAY」をしたりしてホスト側のDISPLAY環境変数を手に入れて、コンテナのDISPLAY環境変数に代入します。実行することでウィンドウがとんできます。

$ export DISPLAY=(ホスト側のDISPLAY)
$ python3 /src/wx_example.py

とんできたウィンドウはこんな感じです。

コンテナから飛んできたウィンドウ

コンテナのイメージ化

動作確認ができたら、exitコマンドでコンテナを停止してください。 コンテナを停止したら、「docker container commit <コンテナ名><イメージ名> 」 で、コンテナのイメージ化ができます。

$ docker container commit pedantic_williams wx_ubuntu2004

ちなみにイメージ名は大文字にするとエラーが出ますので、全て小文字にしてください。 作成されたイメージは「docker image ls」で確認ができます。

作成したイメージのタグ付け

DockerHubにイメージを登録するには名前を「<ユーザー名>/<イメージ名>:<タグ名>」 という形式でないといけません。イメージ名とタグの変更は「docker image tag <元のイメージ名><新しいイメージ名> 」 でできます。

$ docker image tag wx_ubuntu2004 setoti/wx_ubuntu2004

タグ名は省略すれば「latest」というタグ名になります。元のイメージ名は削除されずに残っていますが、「docker image ls 」でimageを確認するとIMAGE IDは同じなので実体も同じとなっています。

DockerHubへのPush

さきほど作成したイメージをDockerHubに登録します。DockerHubへイメージをpushするにはDockerHubアカウントが必要なので登録しておいてください。 アカウントができたら「docker login 」でDockerHubへログインします。ログイン状態で「docker image push <イメージ名>」をすれば、リポジトリが作成されてアップロードされます。

$ docker login
$ docker image push setoti/wx_ubuntu2004

他人が自分のイメージを使う場合

イメージをアップロードすることができましたので、Dockerをインストールしていれば、他の人は簡単に環境構築ができます。 例えば、このブログで登録したimageは以下のコマンドで使うことができます(dbind-WARNINGが出るかもしれませんが、GTKライブラリによるものなので気にしないでください)。

$ docker container run --rm -e DISPLAY=$DISPLAY setoti/wx_ubuntu2004 python3 /src/wx_example.py

「--rm」はコンテナが停止したらコンテナを即破棄するオプションです。コンテナは停止しても残り続けるため、 例のように一つのコマンドを実行するときとかに使います。また、「-e」で環境変数付きで起動することができます。

他人が書き換えたソースの動作確認する場合

他人が書き換えたソースコードの動作確認をする場合、例えば、以下のようにすればいいです。

$ docker container run --rm -e DISPLAY=$DISPLAY --mount type=bind,source=<srcまでの絶対パス>,target=/src setoti/wx_ubuntu2004 python3 /src/wx_example.py

「--mount type=bind,source=<ホストのディレクトリ>,target=<コンテナのディレクトリ> 」をつけることで、 ホストのディレクトリをコンテナのディレクトリにマウントします。<ホストのディレクトリ>のパスは絶対パスでないと駄目です。 指定ディレクトリに書き換えたwx_example.pyを置けば、コンテナの環境で動作確認ができます。

コンテナやイメージの削除

最後に後始末として停止しているコンテナを削除します。停止しているコンテナの確認は 「docker container ls -a」でできます。「-a」のオプションを付けることで停止しているコンテナも表示されます。 確認できたら、「docker container rm <削除するコンテナ名>」で削除できます。また、「docker image rm <削除するイメージ名>」でイメージの削除ができます。

$ docker container ls -a
(停止中のコンテナも含めて表示される)
$ docker container rm pedantic_williams
$ docker image ls
(イメージ名が表示される)
$ docker image rm wx_ubuntu2004 

例では、変更前のイメージ名を消しています。名前変更後のイメージsetoti/wx_ubuntu2004があるので実体は消されません。

おわりに

ここに書いた方法によるイメージの作成はあまり推奨される方法ではないみたいです。アプリ開発や運用では DockefileやDocker Composeを使うことが推奨されるようです。ただ、私的に使うにはこれでいいかなと思います。

参考文献

[1] 伊藤祐一、「たった1日で基本が身に付く! Docker/Kubernetes超入門」、技術評論社、2020年

お問い合わせフォーム プライバシーポリシー

© 2021 Setoti All rights reserved.