Microchip MPLABX/MPLAB harmonyでUSBオーディオデバイスを作る(前編)
必要に駆られ、PCからUSBオーディオデバイスとして見えるブツを作ることに。
数社のデバイスをピックアップし一通り試したのですが、PIC32を選定しました。
PICを選定した理由とか思った事とか、嵌まった所などメモ。
このブログの最後に今回作ったプロジェクトのgithubリンクを掲載しています。
MPLAB harmony
いつのまにかMicrochipからPIC32専用ファーム開発環境フレームワークが出ていました。
MPLAB Harmony | Home | Microchip Technology Inc.
MPLABXが採用しているNetbeansの機能に完全に支配されたフレームワークのようですが、使ってみた所感としてはMicrochip Libraries for Applications(MLA)のifdef地獄ライブラリから開放され、デバイススペシフィックにコードが生成できるため見通しは非常に良いです。
また生成されたアプリケーションはステートマシンが状態遷移をするコード構成になっておりRTOS移行時も比較的楽にできそうです。
またGPIOの入出力設定、初期状態のピンの論理もGUIで設定でき、かなり助かってます。(←ここ重要)
といいつつも今回のようにUSB周りを詳細にいじる場合はUSBデバイスディスクリプタなどスタック構造の知識を少し持っておかないといません。これは開発が容易といわれているCypress PSoC Creatorなどでも同様なので、設計自由度の高いUSBデバイスを作る開発環境では宿命なのかもしれません。しかしながらMPLAB harmonyもサンプルプロジェクトが豊富にあるのでそれを参考にできます。
PID/VIDでケチがつかないデバイスを作ることができる
MicrochipはNXPやFTDIなどと同様にUSBコントローラが自社デバイスであり、該当プロダクトの生産台数が10000台に達しないという条件の下、PIDをマイクロチップのVIDとともに使用する許可を申請できます(この情報について信頼を置ける権威ある文章は本家の英文です。必ず本家を参照のこと)https://www.microchip.com/usblicensing/Default.aspx
そのためオリジナルUSBデバイスで少量のデバイスしか作らない場合、PID/VIDにかかわる問題でケチがつかないデバイスを作ることができます。
使用したソフト/ハードなど
MPLAB harmonyはバージョンによってAPIが変わる可能性があります。このブログの内容を検証する際には以下の組み合わせを使用してください。
(インストール手順は割愛します。MPLAB harmonyはインストール後にMPLABXからロードできるようにする必要があります。この方法はMPLAB Harmonyのインストール完了後の画面で説明されます。)
使用したPICは秋月でお安いPIC32シリーズのPIC32MX220F032Bです。
PIC32MXは残念ながら正規の手法でUSBFS通信を行うには外部クロックが必要となります。16MHzのオシレータを接続しました。
また外付けオーディオDACを接続しようと思いますが、前編では、USBデバイスとして認識されるところまでの確認をしたいので、LEDを1つ、確認用に用意するだけにします。
参考回路図
サンプルコードaudio_speakerを参考にUSBデバイスディスクリプタを修正
USBスピーカサンプルがharmonyに同梱されています。ターゲットデバイスがPIC32MZ2048なのでちょっと違うのですが参考になります。
参考にするのはsystem_init.cの"USB Stack Configuration"(123~470行目です)
このサンプルプロジェクトをPIC32MX220F032B用に書き換えていくという手もありかもしれませんが、構成が複雑なのでやめました。
まず新規にPIC32MX220F032B用にMPLABXプロジェクトをつくり、Tools->Embedded->MPLAB harmony Configurator を起動しMBLAB Harmony Configuratorタブで設定を行います。
変更するのは
USB Library
Device & Project Configuration
の二つです。
USB Libraryは下図の青でマーカーされている部分が変更点です
Use USB Stack? -> check
Number of Endpoints Used: 2
USB Device Instance 0 -> check
Function 1 -> check
Number of Interfaces: 2
Device Class: AUDIO
Device & Project ConfigurationではPIC32MX220F032Bのクロックの設定がメインとなっています。ここが間違っているとどんなにがんばっても「USBデバイスが認識されません」とWindowsから言われてしまいます.
Device & Project Configuration ->
PIC32MX220F032B Device Configuration->
DEVCFG2->
FPLLDIV: DIV_4
FPLLMUL: MUL_20
UPLLDIV: DIV_4
UPLLEN: ON
FPLLODIV: DIV_1
DEVCFG1->
FNOSC: PRI
FSOSCEN: OFF
IESO: OFF
POSCMOD: HS
FPBDIV: DIV_1
FWDTEN: OFF
DEVCFG0
ICESEL: ICS_PGx2
PWP: 0xff
ポートの設定も行います。USBはポート固定されていますが、LEDを接続したRA0を出力に設定します。
MPLAB Harmony Pin Diagramタブを選択し、下部のMPLAB Pin Tableで、
Package: SPDIP
を選択し、Flagsをクリック。
RA0の項目を
TRIS: OUT
LAT: Low
を選択しウィンドウを閉じる。
ここまでできたら、Configurationボタンを押し、保存後Generateでコードを生成します。初回はOverwrite local changesにチェックをしても良いと思いますが、今後はsystem_init.cを手動で変更するので気をつけます。ここにチェックをしない場合、コードを変更してよいかGeneratorが聞いてきます。
生成後、先ほどのサンプルコードのsystem_init.cの"USB Stack Configuration"(123~470行目です)をコピーペーストしてしまいます。
メインとなるコードはapp.cです。ここにステートマシンが記述され、USBの状態により遷移するコードを記述します。
なお、audio_speakerのサンプルコードを元にapp.cを作りました。後述するgithubからダウンロードしてください。
今回LEDのみで実験する前提でした。なのでとりあえず動作(してるんだろうな?…って)確認ができる用のコードを記述します。
Windows上でオーディオスピーカをミュートする度にLEDがON->OFF->ONを繰り返すコードをapp.cに記述します。
app.cの276行目付近の”case USB_DEVICE_AUDIO_EVENT_CONTROL_TRANSFER_DATA_RECEIVED:”の部分を記述します。
case USB_DEVICE_AUDIO_EVENT_CONTROL_TRANSFER_DATA_RECEIVED: USB_DEVICE_ControlStatus(appData.usbDevHandle, USB_DEVICE_CONTROL_STATUS_OK ); if (appData.currentAudioControl == APP_USB_AUDIO_MUTE_CONTROL) { SYS_PORTS_PinToggle(PORTS_ID_0, PORT_CHANNEL_A, 0); // <- これを追記 appData.state = APP_MUTE_AUDIO_PLAYBACK; appData.currentAudioControl = APP_USB_CONTROL_NONE; //Handle Mute Control Here. } break;
コンパイル、書き込みを終え、PIC32MXをUSBで接続し、Harmony USB Speaker Exampleが認識できることを確認する。
ミュートするたびにRA0に接続したLEDが点灯→消灯を繰り返すはずです。
今回作ったMPLABXプロジェクト
FlashAir W-03シリーズからTwitterにポストする
変態SDカード一族
GPIOがブラウザなどから自在に操れてしまう一部の頭がおかしい人たちに人気のFlashAirですが、
W-03(SD-WEシリーズ)からLuaスクリプトが動くので僕のような電子工作やネットワーク初心者でも格段に自由度の高いお遊びができます。わーい
https://flashair-developers.com/ja/documents/api/lua/
というわけで、さくっとTwitterにFlashAirのLuaスクリプトからポストしてみました。今回もStewgate-Uを使います。
TwitterにポストするのでFlashAirからも”外の世界”にアクセスできないといけません。FlashAirをインターネット同時接続モードにします。
不可視フォルダになってる/SD_WLAN/CONFIGを以下のように修正します。
APPMODE=6 APPNAME=myflashair APPSSID=flashair APPNETWORKKEY= VERSION=FA9CAW3AW3.00.00 PRODUCT=FlashAir VENDOR=TOSHIBA LOCK=1 DNSMODE=0 BRGSSID=無線LANのSSID BRGNETWORKKEY=無線LANのパスワード APPAUTOTIME=0
保存します。
ルートディレクトリにLuaスクリプトを書きます。Twitterは同じメッセージを連投できないのでNICTからプレーンテキストでJST時刻を取得しメッセージの末尾にします。
Stewgate-Uについてはこちら
bb,cc,hh = fa.request{url = "https://ntp-a1.nict.go.jp/cgi-bin/time", method = "GET" } contenttype = "application/x-www-form-urlencoded" msg = "うんこ" mes = "_t=ここにStewgate-Uから発行されたトークンを書く&msg="..msg .. " posted at ".. tostring(bb) blen = string.len(mes) b,c,h = fa.request{url = "http://stewgate-u.appspot.com/api/post/", method = "POST", headers = {["Content-Length"] = tostring(blen), ["Content-Type"] = contenttype}, body = mes } print(tostring(b))
これをscript.luaなどという名前でFlashAirのルートディレクトリに保存します。(UTF-8 BOM無しエンコードで保存)
FlashAirを一度アンマウントし、再マウントします。
しばらくすると”flashair”というSSIDが出現するのでPC等で接続しておきます。
ブラウザでhttp://myflashair/script.lua にアクセスすると…
ミッション成功
おしまい。
Quartus II 14.0/14.1で初期値ありのUFMをインスタンシエートする
以前(アルテラCPLDで150LE未満のワンチップCPUを作ったお話 - Lynx-EyEDの電音鍵盤 新館)のときはQuartusII 13.1ベースで記事を書いていたので、14系列でかなりの変更があったと聞き、どう変わったか確認したときのメモです。
MegaWizardが消えたっぽい
MAX10 FPGAのUFMがAvalon-MMのモジュールとして使用できるようになっており(Altera On-Chip Flash)、その流れを受けてかMAX II/VのUFMもQsys経由でプロトコルや初期値のセットアップができるようになっています。
といってもMAXII/VのUFMはAvalon-MMのモジュールにならないので、後述するエクスポートするだけになります。
Qsysを今まで使っているなら特に問題ないと思いますが、Quartus IIでMAXII/Vのプロジェクトをつくり、Qsysを起動します(IP Catalogから起動してもOK)
UFMをSPIスレーブ、16bit,read onlyで初期化するので
画面左のIP Catalogからツリーをたどり、
Library -> Basic Functions -> On Chip Memory -> Altera USer Flash Memory for SPI Interface Protocol
を選択し、[add]ボタン
プロトコルの詳細設定画面が出るので[General]タブで
Access mode : read only
Configuration mode : Extended mode (16bit address and data)
Port settings: Use 'osc' output port
にチェック
次に[Initialization and Simulation]タブで
Memory Content Initialization: Initialize from hex or mif file
にチェックし、所望の初期値ファイル(ここではプロジェクトのルートにあらかじめ用意したUFM_Module.hex)を指定する。(後述:ここで問題が発生する)
して[Finish]する。
QsysのSystem Contentsタブに戻り、
Avalonバス上につながるコネクションは今回ひとつもないので、すべてダブルクリックしてExportしておく。
ここからはQsysのいつもの使い方なので駆け足で。
メニュー -> Generate -> Generate HDL -> Generate
Finish(Qsysを任意の名前で保存するのを忘れずに。)
Quartus II
Project -> Add/Remove Files in Project
で先ほどのQsysファイルをインポートする
インスタンシエート。自分はこんな感じでやりました
qsys_ufm u0 ( .ufm_spi_0_ncs_ncs (ufm_ssel), // ufm_spi_0_ncs.ncs .ufm_spi_0_osc_osc (int_osc), // ufm_spi_0_osc.osc .ufm_spi_0_sck_sck (ufm_sclk), // ufm_spi_0_sck.sck .ufm_spi_0_si_si (ufm_mosi), // ufm_spi_0_si.si .ufm_spi_0_so_so (ufm_miso) // ufm_spi_0_so.so );
hexの中身が反映されない
これでコンパイルするとUFMに指定した初期値が反映されておりませんでした。
警告127003というのが目に付いたので見てみますと
127003: Critical Warning (127003): Can't find Memory Initialization File or Hexadecimal (Intel-Format) File C:C:/altera/14.1/projects/ufm_module/altera14.1projectsufm_moduleUFM_Module.hex -- setting all initial values to 0
確かに初期値が読み込めてないっていうか、ファイル名バグってないですかこれ?System::Strings::Split('\')でも使って再連結するときに忘れたのかな(ぇ
対処:
プロジェクトフォルダからの相対パスでファイル名を指定する
自分の場合はプロジェクトのルートフォルダにUFM_Module.hexという名前で保存してあるので、
./UFM_Module.hex
と記述し、先ほどと同じようにGenerate HDL -> Finish
コンパイルすれば、先ほどの警告はなくなり、初期値が反映される。
ちなみに
C:\altera\14.1\my_project_folder\my_project\my_ufm_data.hex という階層にhex/mifがある場合
C:\\altera\\14.1\\my_project_folder\\my_project\\my_ufm_data.hex
って書き直してやると動く模様。
でも、今後修正されたときにどんな挙動するかわかんなくなるし、やめたがいいかも。
JavaScriptでStewgate U経由でTwitterにポストする
半年近くブログ書いてない...
よく、OAuth非対応なArduinoやmbedや組み込みLinux機器から投稿する例は見るのですが、プレーンなhtml+JSでStewgate使うにはどうしたらいいのかなと悩んで、解決した後すぐ忘れてしまうのでメモ書き。
StewGate U: Throw your stuff into the stewittering pot.
ツイッタアカウントでログインすると、トークンというものがもらえます。
使い方 以下のURLにPOSTデータを送ると、StewGate UからTwitterアカウントに ポストできます: http://stewgate-u.appspot.com/api/post/ POSTデータ: _t = (ここに取得したトークン) msg = "あなたの送信したいメッセージ" * UTF-8、URLエンコード済みにしてください。 * メッセージに "%(name)s" を含めると、トークンの名前に置換されます。 応答: OK (ポスト成功) エラーメッセージ (ポスト失敗)
と、あるので、(以下に続く)
XMLHttpRequestを使った例
というわけで、トークンをもらったら以下のコードを書きます。投稿後のレスポンスを取得したかったのでXMLHttpRequestを使いました。
var fd = new FormData(); fd.append("_t", "(取得したトークン)"); fd.append("msg","(ポストしたいメッセージ)"); var xhr = new XMLHttpRequest(); xhr.open("POST", "http://stewgate-u.appspot.com/api/post/"); xhr.onload=function(){ (レスポンスxhr.responseを表示するなりなんなり) }; xhr.send(fd);
ちなみにTwitterは同じメッセージを連投できないので、メッセージに投稿時間や乱数を付け加えるのがいいと思います。
コピペ用全文
tweetした時間を投稿します。
スマートフォンなどからも使い易いようにスタイルシートを適用しています。
<html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;" /> <meta http-equiv="Content-type" content="text/html; charset=utf-8" target="_self"> <style> .btn { font-family: Arial, Helvetica, sans-serif; font-size: 28px; color: #fff; padding: 12px 18px; background: #8893da; background: -webkit-gradient( linear, left top, left bottom, from(#8893da), to(#344c77) ); border-radius: 10px; -webkit-box-shadow: 12px 12px 12px rgba(000, 000, 000, 0.3), inset 0px 0px 0px rgba(255, 255, 255, 0); text-shadow: 10px 10px 10px rgba(000, 000, 000, 1), } </style> <script type="text/javascript" charset="utf-8"> function postForm(){ var fd = new FormData(); var date_str = new Date(); fd.append("_t", "(取得したトークン)"); fd.append("msg","ただいまの時刻は"+date_str.getHours()+"時"+date_str.getMinutes() +"分"+date_str.getSeconds()+"秒です。"); var xhr = new XMLHttpRequest(); xhr.open("POST", "http://stewgate-u.appspot.com/api/post/"); xhr.onload=function(){ document.getElementById("stat").innerHTML = xhr.response; // レスポンスの表示 }; xhr.send(fd); } </script> </head> <body> <div> <button class="btn" onclick="javascript:postForm()">POST_XHR</button> </div> <div id="stat"></div> </body> </html>
UTF-8エンコードで保存して、ブラウザから投稿テストします。POST_XHRのボタンを押すとTwitterにポストします。
うまくいけば、投稿ボタン真下にOKと出るはず。
なにか時間経過で変化する事象をツイートする必要があれば、setInterval()などを使って上記関数を繰り返してもいいかもしれません。
アルテラCPLDで150LE未満のワンチップCPUを作ったお話
150LE未満のMISC CPU
スタック1段のみ、キャリーフラグなし、6命令のMISC CPUを作りました。
(ほんとは5命令だったんだけど、このネタに使うのに1命令追加した)
以下が命令です。内部的には最初のコマンド以外、殆ど同じ動きをします。
- GO_IF_NON_ZERO_ELS_SUB スタックがゼロなら引数xxで示したアドレスへ、否ならスタック-xxをスタックに格納。マシン語0x0F,xx
- GET_STREAM_AND_STACK xx bitだけ取得したストリームの下位8bitをスタックする。マシン語0x81,xx
- PIPE_EXTERNAL_TO_FIFO 外部のデータをxxビットだけFIFOにセットする。マシン語0x82,xx
- GET_STREAM_AND_SUBST スタックの値-取得したストリームxxを計算しスタックを上書き。マシン語0x83,xx
- PIPE_SEEK_BITS (通常は命令としては使わない。プログラムを読み込むのにストリームの頭出しシフトをxx bit分するだけ。但し、Nop命令の代わりにはなるかもしれない)。マシン語0x84,xx
- GET_STREAM_PUSH_OUT (これがネタ用に追加した命令)GPIOに引数xxを反映させる。これで思う存分Lチカができるってワケ。マシン語0x85,xx
命令から推測がつくかもしれませんが、シリアルデータを右から左に流したり、条件によって複数あるデータ出力先を切り替えたりする「少し気の利いたFIFO」的なものを作ってました。
しかし条件が増え、デバイスによっては複雑な初期化シーケンスが必要なものもあると、回路規模が増大します。
AlteraのMAXII/MAXVはデバイスの規模にかかわらず1KByteのユーザーフラッシュ(UFM)があるので、初期化手順をここに記述してしまい、回路規模を抑えようというのが経緯です。
冒頭で示した図が、今回作ったCPUのブロック図です。UFMのデータを操作する手段はいくつか提供されていますが、どの手段でもユーザーロジックを消費します。
今回は規模の一番小さいSPI Slaveリードオンリメモリ(16bitアドレスモード = Extended Mode)としてインスタンスしています。回路規模はたったの40LEです。このCPUはこのUFMからコマンドとデータをシリアルリードする設計のため命令体系も回路も独特な構成になっています。
(UFMの取り扱いについてはこちらを参照http://www.altera.com/literature/hb/max2/max2_mii51010.pdf SPIの場合は9-24項以降参照)
メモリ側からはこのCPUがSPI Masterとして見えているので、AlteraでなくてもUFMを持っているLatticeのCPLDや、SPI EEPROMを接続したFPGAなら少しの変更で実装可能なはずです。
使い方
1.コマンドとプログラムを格納するUFMをMegaWizardでインスタンスします。インスタンス方法詳細はhttp://www.altera.com/literature/hb/max2/max2_mii51010.pdf を参照してください。
今回はufm_coreと言う名前でRead Only,Extended modeを選択しました。
2. CPUメインのverilogコードがプロジェクトトップに来るようにします。
なお、コード中の8bitカウンタCountrは今回のブログ記事のためにクロックを分周するのに使っています。必要ない場合は取り除きます。
(コードはブログ最後に記述しました)
3.Lチカ
マシン語でLチカプログラムを書きUFMに保存します。今回の設定ではリードオンリーで実装されているのでQuartus II上でUFMに流し込むバイナリを作ります。アドレスは16bitモードであることに注意してください。
まず、フローを考えます。
(i) スタックに255を格納する
(ii) GPIOに0xFFを出力する(LED全点灯)
(iii) スタックから1減算する
(iv) スタックはゼロ?ゼロでなかったら引数に書かれている(ここでは(iii)の命令が書かれている番地) に戻る。ゼロだったら スタック - 引数の値をスタックに格納(つまり、引数をマイナス値にしたものが格納される)し次の命令へ
(v) スタックに255を格納する
(vi) GPIOに0x00を出力する(LED全消灯)
(vii) スタックから1減算する
(viii) スタックはゼロ?ゼロでなかったら引数に書かれている(ここでは(vii)の命令が書かれている番地) に戻る。ゼロだったら スタック - 引数の値をスタックに格納(つまり、引数をマイナス値にしたものが格納される)し次の命令へ
(ix) スタックはゼロ?ゼロでなかったら引数に書かれている(ここではゼロ番地) に戻る。ゼロだったら→(ここでスタック=ゼロはあり得ない)
これで、8bitの出力全てにLEDが付いていたら、一定周期ごとに全点灯、前消灯を繰り返す事が出来ます。
上記をマシン語にします。16進数表記、ビッグエンディアンです。
(i) 8110 00FF
(ii) 8510 00FF
(iii) 8310 0001
(iv) 0F04
(v) 8110 00FF
(vi) 8510 0000
(vii) 8310 0001
(viii) 0F0B
(ix) 0F00
とまぁこんな感じで記述していきます。LED全点灯/消灯だとつまらないので、もう少し点灯パターンを増やしたのが下記の画面です。
これを実際に動作させてみました。MAX MMKボード上のLEDがちょうど8個あるので使ってみました。もちろんMAX Vでも動作します。(5M160から動作可能)
Minimal CPU on MAX II CPLD - YouTube
追記:せっかくLEDが8個もあるから、POVで「ウンコ」とか表示した方がおもしろかったかな。。。
Verilogコード全文
module ufm_module_top( input clk, input reset, input key, output[7:0] LED ); wire int_osc; reg ufm_ssel; wire ufm_sclk; wire ufm_mosi; wire ufm_miso; reg ufm_miso_reg; wire fifo_sys_clk; reg wait_clock; // STOP SCLK flag reg [7:0] ufm_com_select; // command selector // select= 8'b1xxx_xxxx -> command, select=8'b0xxx_xxxx -> data reg [7:0] ufm_bit_count; // clock counter reg [7:0] user_stack_data; // reg for stack data reg [7:0] user_reg_data; // general purpose reg reg[23:0] ufm_fifo; // 24bit UFM FIFO parameter GO_IF_NON_ZERO_ELS_SUB = 7'h0F; parameter GET_STREAM_AND_STACK = 7'h01; parameter PIPE_EXTERNAL_TO_FIFO = 7'h02; parameter GET_STREAM_AND_SUBST = 7'h03; parameter PIPE_SEEK_BITS = 7'h04; parameter GET_STREAM_PUSH_OUT = 7'h05; // "countr" can be remove if you wish. reg [6:0] countr; always@(posedge int_osc)begin countr <= countr + 1; end // fifo_sys_clk: UFM's clock, upto 9.7-MHz assign fifo_sys_clk = countr[6];//int_osc; always@(posedge ufm_sclk, negedge reset)begin if(~reset)begin ufm_miso_reg <= 1'b0; end else if(~ufm_ssel)begin ufm_miso_reg <= ufm_miso; end else begin ufm_miso_reg <= 1'b0; end end assign ufm_sclk = (wait_clock)? ufm_sclk: fifo_sys_clk; always@(negedge fifo_sys_clk, negedge reset)begin if(~reset)begin wait_clock <= 1'b0; ufm_ssel <= 1'b1; ufm_bit_count <= 8'b0; user_stack_data <= 8'b0; ufm_com_select <= {1'b0, GO_IF_NON_ZERO_ELS_SUB}; ufm_fifo <= 24'b0;//TODO: insert UFM Read command end else if(ufm_bit_count == 0)begin // Command mode or Data mode case(ufm_com_select[7]) 1'b1: case(ufm_com_select[6:0]) // latch the commands GET_STREAM_AND_STACK: begin wait_clock <= 1'b1; // Stop SCLK ufm_ssel <= 1'b0; user_stack_data <= ufm_fifo[15:8]; ufm_com_select <= {1'b1,PIPE_SEEK_BITS}; ufm_bit_count <= 8'd16;//8'd24; end GET_STREAM_PUSH_OUT: begin wait_clock <= 1'b1; // Stop SCLK ufm_ssel <= 1'b0; user_reg_data <= ufm_fifo[15:8]; ufm_com_select <= {1'b1,PIPE_SEEK_BITS}; ufm_bit_count <= 8'd16;//8'd24; end GET_STREAM_AND_SUBST: begin wait_clock <= 1'b1; // Stop SCLK ufm_ssel <= 1'b0; user_stack_data <= user_stack_data - ufm_fifo[15:8]; ufm_com_select <= {1'b1,PIPE_SEEK_BITS}; ufm_bit_count <= 8'd16;//8'd24; end default: begin wait_clock <= 1'b1; // Stop SCLK ufm_ssel <= (ufm_fifo[23]==0 && user_stack_data != 8'h00)? 1'b1 : 1'b0; user_stack_data <= user_stack_data; ufm_com_select <= ufm_fifo[23:16]; // if (ufm_fifo[23]==1 && bit_count == 0) this system freezes. (this can be used this for test) ufm_bit_count <= (ufm_fifo[23] == 1'b0)?8'b0 : ufm_fifo[15:8]; end endcase 1'b0: begin // UFM command data // send command to UFM if(ufm_ssel == 1'b1)begin // if(user_stack_data == 0)jump to address else skip wait_clock <= 1'b0; ufm_ssel <= 1'b0; ufm_bit_count <= 8'd48;//8'd24; user_stack_data <= user_stack_data; ufm_com_select <= {1'b1,GO_IF_NON_ZERO_ELS_SUB}; // change Command mode ufm_fifo <= {16'h0300,ufm_fifo[15:8]};//TODO: insert UFM Read command + address end else begin wait_clock <= 1'b1; // Stop SCLK ufm_ssel <= 1'b0; ufm_bit_count <= 8'd16;//8'd24; user_stack_data <= user_stack_data - ufm_fifo[15:8]; ufm_com_select <= {1'b1,PIPE_SEEK_BITS}; ufm_fifo <= ufm_fifo; end end endcase end else begin wait_clock <= 1'b0; ufm_ssel <=1'b0; ufm_fifo <= {ufm_fifo[22:0], ufm_miso_reg}; ufm_bit_count <= ufm_bit_count - 8'b1; end end assign LED = /*(~key)? ~user_stack_data :*/ ~user_reg_data;//~ufm_fifo[23:16]; assign ufm_mosi = (ufm_com_select[6:0]==GO_IF_NON_ZERO_ELS_SUB)? ufm_fifo[23] : 1'b0; // assign other_pin =(ufm_com_select == GET_STREAM_AND_STACK)? ufm_fifo[8]: other_pin; ufm_core u0( .ncs (ufm_ssel), .sck (ufm_sclk), .si (ufm_mosi), .osc (int_osc), .so (ufm_miso) ); /* input ncs; input sck; input si; output osc; output so; */ endmodule
◆参考
めくるめくギリギリCPUの世界 - .mjtの日記復帰計画
http://www.altera.co.jp/literature/hb/max2/max2_mii51010_j.pdf
http://www.altera.co.jp/literature/hb/max-v/mv51007_j.pdf
SigmaDSP ADAU1781のトライアルボード
SigmaDSP評価
前回(コーディング不要のSigmaDSPを試食する - Lynx-EyEDの電音鍵盤 新館)ADAU1701/ADAU1401Aで実験したとき、動作中は人肌くらいの温度まで出ており、擬似差動入力に同相信号を入力したとき、実測で0.1~0.5mVppほどの規則性の無いノイズが確認されていました。
万能基板上の実験だったのでアテにはなりませんが、省電力、低ノイズを謳ってるADAU1781をターゲットに再チャレンジするべく基板を起こしてみました。*1
ホストCPUはPSoC4です。コンフィグ、I2Sマスタとして動作します。
600mil幅、4層基板です。
でとりあえず、完成。
ADAU1781はAD/DAがそれぞれ2chのみですが、外部デジタル入力(PDM,TDM)に最大8chまで対応でき、クロック入力周波数が固定されていなかったりと、便利です。今回はPSoC4内部で24MHz信号をD-FFを通し12MHzをつくりADAU1781に与えています。
PSoC4のUDBが少ないのと、SCBの制約から、SigmaDSPのコンフィグはSPIを利用しています。
これからの方針
しばらくこのボードのアナログ特性の評価をしますが、PSoC4の動作速度の都合上、I2Sの通信速度はあまり早くありません。96kspsのデータをすばやく取得するのにMAXVの利用を考えています。Avalon-STインターフェースにぶら下げられるI2Sスレーブとしての利用、UFMからのSigmaDSPコンフィグレーションなどができると便利そうです。
*1:ほんとは評価基板購入を考えていましたが、意外に高い
GLFW3で等高面に応じた色の3次元グラフを作る
前回の続き。複数のセンサーの出力結果と相関を時間軸でみるのには3次元グラフが便利かなということでいろいろググったらこんなサイトに
OpenGL + GLFWで表示してみる
Z軸の等高面で色が変化するようになっていますが、あまり真面目に作っていません。上記のコードのようにswitch~caseで行うか、HSV→RGB変換を扱う範囲の最大最小値で正規化してやるほうがよいと思います。詳細はget_color関数をご覧ください
グラフのメモリがついてないのはまた後でということで。
式はfunc_t関数内の式を計算して表示しています。
#define GLFW_INCLUDE_GLU #include <math.h> #include <stdio.h> #include <GLFW/glfw3.h> #include <Windows.h> #include <locale.h> #pragma comment(lib, "GLFW3.lib") #pragma comment(lib, "opengl32.lib") #pragma comment(lib, "glu32.lib") #define MIN -5.0 // Z軸の最小 #define MAX 5.0 // z軸の最大 #define DX 0.8 // ポリゴンの大きさ。あまり小さいと動作が緩慢に、大きすぎるといびつな形になる #define DY 0.8 // 同上 #define MATH_PI 3.141592653589793238662643383279 static double eye_z = -70.0; int ROTATE_INDICATE; // 1: rotate , 0: do not rotate float a2rad(double angle) { return (float)(angle / 180 * MATH_PI); } double r2deg(float radian) { return (double)(radian * 180 / MATH_PI); } double func_t(double x, double y){ return 4.8 * cos(sqrt(x * x + y * y) / 1.0); } void get_color(GLubyte *color, double z) { color[0] = (GLubyte)fmod(((z - MIN) * 255 / (MAX - MIN) + 250) , 256); color[1] = (GLubyte)fmod(((z - MIN) * 255 / (MAX - MIN) + 50) , 256); color[2] = (GLubyte)fmod(((z - MIN) * 255 / (MAX - MIN) + 100) , 256); } void reshape(int width, int height) { static GLfloat lightPosition[4] = { 0.25f, 1.0f, 0.25f, 0.0f }; static GLfloat lightDiffuse[3] = { 1.0f, 1.0f, 1.0f }; static GLfloat lightAmbient[3] = { 0.25f, 0.25f, 0.5f }; static GLfloat lightSpecular[3] = { 1.0f, 1.0f, 1.0f }; glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glShadeModel(GL_SMOOTH); glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0, (double)width / (double)height, 0.1, 100.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(0.5, 1.5, eye_z, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0); glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse); glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient); glLightfv(GL_LIGHT0, GL_SPECULAR, lightSpecular); } void draw_polygon(double x0, double y0, double z0, double x1, double y1, double z1, double x2, double y2, double z2, double x3, double y3, double z3) { GLubyte color[3]; glBegin(GL_POLYGON); get_color(color, z0); glColor3ub(color[0], color[1], color[2]); glVertex3d(x0, y0, z0); get_color(color, z1); glColor3ub(color[0], color[1], color[2]); glVertex3d(x1, y1, z1); get_color(color, z2); glColor3ub(color[0], color[1], color[2]); glVertex3d(x2, y2, z2); get_color(color, z3); glColor3ub(color[0], color[1], color[2]); glVertex3d(x3, y3, z3); glEnd(); } void edit_xyz_axis(void){ glBegin(GL_LINES); glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(50.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f); glColor3f(0.0f, 1.0f, 0.0f); glVertex3f(0.0f, 50.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f); glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(0.0f, 0.0f, 25.0f); glVertex3f(0.0f, 0.0f, 0.0f); glEnd(); } void display_polygon(){ static GLfloat diffuse[3] = { 1.0f, 1.0f, 1.0f }; static GLfloat ambient[3] = { 0.25f, 0.25f, 0.25f }; static GLfloat specular[3] = { 1.0f, 1.0f, 1.0f }; static GLfloat shininess[1] = { 32.0f }; static float angle = 0.f; glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse); glMaterialfv(GL_FRONT, GL_AMBIENT, ambient); glMaterialfv(GL_FRONT, GL_SPECULAR, specular); glMaterialfv(GL_FRONT, GL_SHININESS, shininess); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_COLOR_MATERIAL); glEnable(GL_DEPTH_TEST); glClearColor(1.0f, 1.0f, 1.0f, 1.0f); if (ROTATE_INDICATE == 1){ ROTATE_INDICATE = 0; angle += 2.5f; if (angle > 360.0f) { angle -= 360.0f; } } if (ROTATE_INDICATE == -1){ ROTATE_INDICATE = 0; angle -= 2.5f; if (angle < 0.0f) { angle += 360.0f; } } glRotatef(angle, 1.0f, 1.0f, 0.0f); glPushMatrix(); double x, y; for (y = -25; y <= 25; y += DY) for (x = -25; x <= 25; x += DX) { draw_polygon(x, y, func_t(x, y), x + DX, y, func_t(x + DX, y), x + DX, y + DY, func_t(x + DX, y + DY), x, y + DY, func_t(x, y + DY)); } edit_xyz_axis(); glPopMatrix(); } void idle() { Sleep(10); } void error_callback(int eror, const char* description) { fputs(description, stderr); } static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods){ if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) glfwSetWindowShouldClose(window, GL_TRUE); else if (key == GLFW_KEY_PAGE_DOWN && action == GLFW_PRESS) eye_z = eye_z - 2.5; else if (key == GLFW_KEY_PAGE_UP && action == GLFW_PRESS) eye_z = eye_z + 2.5; else if (key == GLFW_KEY_DOWN && action == GLFW_REPEAT | GLFW_PRESS) ROTATE_INDICATE = -1; else if (key == GLFW_KEY_UP && action == GLFW_REPEAT | GLFW_PRESS) ROTATE_INDICATE = +1; } //static void mouse_callback(GLFWwindow* window, ) LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ switch (uMsg) { case WM_DESTROY: PostQuitMessage(0); return 0; default: break; } return DefWindowProc(hwnd, uMsg, wParam, lParam); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nShowCmd) { int width, height; // コールバックの設定 glfwSetErrorCallback(error_callback); // GLFWを初期化 if (!glfwInit()) exit(EXIT_FAILURE); GLFWwindow* window = glfwCreateWindow(400, 400,"3D-graph display test", NULL, NULL); if (!window) { glfwTerminate(); exit(EXIT_FAILURE); } glfwMakeContextCurrent(window); glfwSetKeyCallback(window, key_callback); // メインループ while (!glfwWindowShouldClose(window)) { glfwGetFramebufferSize(window, &width, &height); reshape(width, height); idle(); display_polygon(); glfwSwapBuffers(window); // イベントのポーリング glfwPollEvents(); } // Windowの削除 glfwDestroyWindow(window); // GLFWの終了処理 glfwTerminate(); exit(EXIT_SUCCESS); }
結果
PageUp / PageDownで拡大縮小
カーソルキー上下で回転します
結論
GLFWはGLUT同様ウィンドハンドラ周りの心配しなくても面倒見の良いライブラリだけど.Net環境でダイアログベースで開発できなくてつらい。なのでGLFW使ったお勉強はここまでにしようと思います。
◆参考
c++ - Using GLFW to render and WinAPI to handle messages - Stack Overflow