2023年8月27日日曜日

自作CPUで遊ぶ 5

これまでのプログラムは1個のタイマーを使いタイマー割り込みハンドラ内で割り込み周期を段階的に変えることでモーターの駆動パルスを作っていたので制御が粗くなってしまっていた。 そこで今回はやり方を変えて、タイマー割り込みの周期は一定とし代わりにモーター制御用のパルス生成器を作ることにした。
パルス生成器の構造はシンプルにアキュムレータ方式とした。アキュムレータを acc とすると、accは毎クロック acc = acc + αで更新される。また、acc が閾値 N を超える場合は0に戻す。この時パルスが1つ出力される。下図はaccのαが1, 2, 3の場合のaccの軌跡を表している。現在回路は100MHzで動かしているのでNは100_000_000に設定する。すると、αが1の場合1秒に1回パルスが発生し、αが2の場合1秒に2回、αが3の場合は1秒に3回発生する。つまり、αはパルスの周波数を設定することになる。今回のモーターの場合パルス12発で1回転するので、モーターの回転数を RPS (Rotate Per Second)で表すとするとαは 12×RPS になるがこの変換はIP内で行うことにした。つまり、ソフトウェアは秒当たりの回転数を何らかの方法で算出して設定すれば良い。
次に回転数の生成だが、前回採取した波形から起動時の立ち上がりの部分(左下の緑色の曲線)を式化してそれを使って回転数を決定してみることにした。
色々試してみた結果、y = ax^4、a = 1.97499e-11 を使うことにした。 多項式にすると計算に時間が掛かるのでなるべく単純な式にした。タイマーの割り込み周期は1msecにして、割り込みハンドラで y = ax^4 でモーターの回転数 RPS を計算してIPに設定する。 aの値が1.97499e-11とかなり小さいので固定小数演算にすると32bitを超えてしまう。そこで計算はfloatで行うことにした。zumi32-gccはfloatも使えるのでfloat演算自体は問題ないが、zumi32の演算用リソースとしてはバレルシフタのみなのでコンパイル後のマシン語のステップ数と演算に要する時間が気になるところだ。 そこで、コンパイルしたマシン語列をシミュレーションで流してみたところ、割り込み処理時間は約28usecであることが判った。タイマー割り込み周期は1msecにするのでまったく問題ない。(シミュレーションではタイマの割り込み周期は50usにしている。)
で、これを実機で動かしてみたのだが結果はNGだった。起動直後に脱調してしまう。aの値を小さくしたりx^4をx^2にしたりして立ち上がりを遅くすると動くのだが最大回転数は50 [rps](3000 [rpm])が上限だった。そこで、モーターのdrv_A, drv_B, drv_Cの駆動パターンを変えてみることにした。具体的には従前は 011、110、101のパターンを繰り返しており常に2つのコイルを励磁するようにしていたのだが、これを001、011、010、110、100、101と1コイルのみが励磁されるパターンを入れることにした。 これにより今までは12パルス@1回転だったのが24パルス@1回転になるので、IPの設定値を12倍している部分は24倍に変更した。 この他にaの値を色々調整したりした結果 120[rps](7200 [rpm])まで動くようになった。 ヤッタ!!
以下はロジアナで採った波形だ。
Sensorの周期が120Hzなので7200[rpm]で回転していることが判る。
以下は動かしている様子。7200[rpm]ともなると風切り音がすごい。

ということで、zumi32でモーターを制御して7200 [rpm]で回転させることが出来た。また、zumi32のプログラムでもfloat演算を問題なく使えることを確認することが出来た。\(^_^)/

2023年8月13日日曜日

自作CPUで遊ぶ 4

ジャンク箱を確認したら今回使っている以外にもHDD用スピンドルモーターが5個出てきた。電極は殆どが4極だが1つだけ3極の物があった。電極間抵抗を測ると全て同じ値なのでコイルはデルタ型の接続になっているんだろう。
コイル抵抗は全部違っていて(多分品種毎に異なる)、Nidecのが1.8Ω、JVCのが1.6Ω、CAO*が3.3Ω、JBY*が1.2Ωだった。これらはコモン端子とその他の端子間の抵抗値なのでコイル1個の抵抗値だ。また、デルタ型の電極間抵抗は2.8Ωだった。デルタ型の電極間は Rと2R の並列回路として見えるのでR1個当たりに換算すると、2.8Ω = r // 2r = r(2/3) → r = 2.8 (3/2) = 2.8 * 1.5 = 4.2Ω の筈だ。抵抗値が大きいということはコイルの巻数が多いことを意味するのか?もしそうなら抵抗値が大きい方が回転数も大きいかも知れない。。。(ホントか?) 後で動かしてみようと思う。 モーターの駆動波形と実際のモーターの回転数を見たくなったのだが、所有しているデジタルオシロスコープは2CHでCH数が足りないのと、ちょっと測定結果をグラフ化したりしたかったのでロジック・アナライザーで測定することにした。このロジアナは2014年にこのブログで開発したもので、Lattice社のMachXO2-7000評価ボードにロジアナの自作IPを実装した。アクイジションメモリとして16MBのSDRAMをウラ面に無理やり実装している。(以下は当時撮影した写真)
SDRAMは150MHzで動かしている。これに対してロジアナはCH数24、サンプリング速度は100Mspsなので一見スループットが不足するように見えるが、このロジアナは取得したデータをValue Change方式でSDRAMに格納する。この方式はデータ圧縮の効果があり(詳細は当時のブログを参照) 圧縮の程度はデータの変化率に依存するが常にサンプリング周波数に近い速度で変化する信号を測定でもしない限りはまあまあ使える。  このロジアナで使用した評価ボードはLH2232HLを搭載しておりPCとはこれを介してUARTで通信する。UARTは8Mbpsで動かした。また、この後にMachXO2-7000搭載のFPGAボードを自作しこれにもUSB I/F用にLH2232HLを搭載したがFPGAとは非同期FT245モードでも通信出来るようにした。 このモードは非同期8bitパラレルでデータをやり取りするので測定データのアップロードを高速に行うことが可能であり、実際UARTモードよりも6.7倍程度高速に転送できた。今回はモーターの起動からSDRAMの最大容量までデータを取得したい。そのためアップロードするデータ量も多くなるので上記写真の機体ではなく後者のFT245モードが使える方のボードを使用することにした。
モーターの回転数はフォトインタラプタを使って測定することにした。具体的には1箇所穴の空いたボール紙で作った円板をモーターに貼り付け、この穴をフォトインタラプタのスリットに通過させた。
ロジアナの制御は専用のGUIアプリで行うが、久しぶりに起動しようとしたら使っているwxWidgetsのライブラリのバージョンが古すぎて起動しなかったため wxformbuilderでGUIを再編集・生成してビルドし直した。信号はモーターの駆動信号(drv_A, drv_B, drv_C)とフォトインタラプタの信号(Sensor)を採取した。 SDRAMを全領域使って起動時から11秒程度の期間のデータを取得できた。単純に100Mspsの速度でデータを取得した場合、(4*1024*1024)/100e6 ≒ 42msecの期間しか採取できないが、上述の圧縮効果により260倍の期間のデータを取得出来ている。
このGUIアプリには波形の表示機能は無い。その代わりVCDファイルを出力するのでこれをGtkWave等を使って波形表示を行う。以下は取得した信号の波形だ。モーターに貼り付けた円板の穴は1つだけなので1パルス/1回転発生する。従って、このパルスの周期を測れば回転数を求められる。また、前回 drv_A, drv_B, drv_Cに1秒毎にパルスを与えてモータパルス12個で1回転するとしたが、波形からこれが正しかったことが判った。
GtkWaveのExport機能を使って波形データをTIM形式でファイルに出力し、テキストエディタで信号毎のCSVファイルに編集してlibreofficeのcalcに読み込み、回転数に変換してグラフ化した。横軸は起動してからの経過時間、縦軸は回転数 [rpm] だ。
緑色のグラフ(real)はフォトインタラプタの信号から算出した回転数、オレンジ色のグラフ(drive)はdrv_Aの周期から算出した回転数だ。 これを見るとFPGAの駆動に対してモーターの応答に遅れがあることが判る。また、回転速度の上げ方が粗すぎるようだ。モーターの回転数にリンギングのような振動があるのも判る。このリンギングはモーターをちゃんと固定していないのと円板が若干偏心しているのも影響している可能性がある。何れにしろ、測定の分解能が高いのでこの辺の挙動が観測出来ている。なかなか面白い。  … でだ。 どうやったらもっと旨く動かせるんだろう? この系で測定しながら試行錯誤すれば出来るのかしら?


2023年7月30日日曜日

自作CPUで遊ぶ 3

ZUMI32でHDD用のスピンドルモータを制御してみた。使用したモーターはジャンクのHDDから抽出したやつで電極が4つある。各電極間の電気抵抗を測ると1つの電極に対して他の3つの抵抗値が同じになるのでコイルはスター型の接続になっているようだ。 FPGAの出力でモーターのコイルを直接ドライブ出来ないので、Power FETを介してドライブした。ちょうど手持ちにSanKenのFKI10531というFETがあった。このFETはVGS(th)が2.0V (typ)なのでFPGAで制御するのに都合が良い。
3相モーターなので駆動は2つのコイルを同時にONにしその状態を順繰りに切り替えていく方式にした。CPUからの制御はタイマー割り込みを使用して割り込みハンドラ内で行うようにした。 何発のパルスでモーターが1回転するのか不明だったので割り込み周期を1秒にしてパルス数と回転の関係を見たところ12個のパルスでモーターが1回転することが判った。 市販のHDDの回転数は5400rpm〜7200rpm位のようだが、今回使用するモーターが載っていたHDDの回転数は不明だ。仮に5400rpmとすると1秒で 5400÷60 = 90 回転 (90Hz、11.1ms)となる。 このモーターを5400rpmで回すには 90Hz × 12 = 1080Hz 、ほぼ 1ms 周期でパルスを発生させる必要がある。つまり、割り込み周期は1msだ。 実際に、その周期でモーターを駆動してみると脱調して回らなかった。パルスモーターと同じように台形駆動(最初は低速で駆動し徐々に回転数を上げていく)が必要なようだ。 色々試してみたところ、パルス周期を30msecから初めて徐々に周期を短くしていくやり方で2.1msecまでは駆動できた。しかし、それ以下にすると脱調してしまう。 円板の有無でも変わるのかも知れない。
CPUの方はまったく問題無い。普通に使えている。

2023年7月23日日曜日

FPGAでLチカ (Spartan7の場合)

ZUMI32でモーターやA/D、D/A等の制御実験をやってみたくなり、ジャンク箱から使えそうな部品を取り出してきた。
DCモーターやHDDのスピンドルモーターや、I2C I/FのA/D、D/A、温度センサやサーミスタ、ペルチェ素子などがあった。やはり人生持つべきものはジャンク箱だ。 DCモーターのPIDによる回転数制御などもやってみたいと考えている。 以下は仮組みした状態。DCモーターの回転数をロータリーエンコーダで測り、PWMでDCモーターを制御する。
この他にも、スピンドルモータの制御やペルチェ素子の制御(温調)などもやってみたいと考えているが、今日は別のことをやってみた。

以前、このブログでFPGAでLチカというのをやった。FPGAの中にリングオシレータを作りその出力でLEDを点滅させるというものだ。 これをCMODS7のSpartan7でもやってみた。 
Vivadoでこの方法でインプリできるのか興味があったのだが、やってみたらインプリできた。 但し、XDCに set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets BTN0_IBUF] という制約を書く必要があった。 トランスペアレントラッチの数だが、色々と試行した結果、このやり方の場合は14600個が上限だった。CMODS7に搭載されているSpartan7はxc7s25csga225-1で、このデバイスのFF数は29200個だがFFでなくラッチとして使う場合は14600個が上限のようだ。
上記を合成&インプリした結果が以下。
FFの使用率は50%だがSLICEとしては全部使われている。
得られた出力波形が以下だ。
周期が15.6usecなので1素子当たりの遅延は単純計算で 15.6/2/14600 ≒ 0.534[nsec] となる。したがって、素子1個のリングオシレータにした場合は約1GHzのクロックが生成できそうだ。 Spartan7のデータシート DS189によるとスピードグレード -1 のCLBのトグル周波数 FTOGは1098MHzとなっているので、ほぼこの値に近い。
リングオシレータを1素子にし、その代わりにそれをGBUFを通してクロックとして2048個のFFによるリングカウンタを駆動して出力が得られるかを見てみたところ出力がトグルしなかった。 次に素子数を2にしてやってみたらこちらは出力された。
周波数は約192.4KHzだった。これの2048倍がクロック周波数になるので 192.4×2048 ≒ 393.2MHzとなった。これはリングオシレータの素子数が2の場合なので、1の場合はこの2倍で786.4MHz。 何故か、最初に求めた値より若干低い。 

この場合はリングカウンタを使ったが、このようにラッチではなくFFとして使う場合は上限がカタログ通りのFF数までは使えるようだ。以下のようにリングカウンタのbit数を29188個にしてもインプリできた。インプリ後のUtilizationを見るとFFの数が何故か29194になっていた。
上記は29188bitのリングカウンタの値を101010.... にしており、これを発振可能な最大周波数で駆動している。このときの消費電流を見たら635mAも流れていた。 素子数を半分にすると406mA、さらに半分にすると239mAになった。電流はUSBの電流を見ている。

2023年7月17日月曜日

自作CPUで遊ぶ 2

今時のデバイスだと、うちの子はどの位の周波数までいけるのかを見てみた。 具体的にはCMOD S7というSpartan7を搭載したDigilentの評価ボード向けにデザインを作ってVivadoでインプリしてタイミングエラーにならないギリギリの周波数を探した。 このCMOD S7はxc7s25csga225-1が実装されている。
インプリするアーキテクチャは以下のようにした。ZUMI32のペリフェラルはINTC(割り込みコントローラ)、TIMER、GPIO (LED & SW)、命令用メモリは8KByte、データ用メモリは4KBとした。 左側のUARTとUIは命令メモリとデータメモリにプログラムをダウンロードするためのモジュールでありZUMI32からはアクセス出来ない。
タイミングエラーにならないギリギリのクロック周波数を探った結果、上限は113MHzだった。但し、これはFPGAのスピードグレードが-1の場合であり、-2だと140MHzまでmetした。 このデザインはリソース使用率が低く、Physical viewを見るとかなり散らばって配置されているので、もっとリソース使用率が高いデザインにするともう少し上の周波数まで行けるんじゃないだろうか? という気がする。
ZYNQでも同様にしてやってみた。以下のようなアーキテクチャにした。 デバイスはxc7z010clg400-1だ。
このデザインでの上限は100MHzと、驚いたことにSpartan7よりも低い値になった。しかし、physical viewを見ると中身はスッカスカでありこれがクロック周波数を下げている要因では無いかという気がする。 もっと混んだデザインにするとmetする周波数は上がるんじゃないだろうか。
最新のデバイスではないが、Lattice MachXO2でもやってみた。ターゲットデバイスはLCMXO2-7000HC-4TG144Cにした。 結果は49MHzが上限だった。リソースの使用率は30〜40%位だった。スピードグレードを5にした場合は53MHzに上がった。
CMOD S7に戻って、プログラムを作って動かしてみた。CMOD S7には3色LEDが1つ、単色LEDが4つ搭載されているのでタイマー割り込みで単色LEDをナイトライダー風に点滅させるプログラムにした。 フォアグラウンド側はmain()でタイマーの初期設定後、割り込みを許可し、その後 while(1); ループに入る。後はタイマーに設定した周期で割り込みが入るので、割り込みハンドラーでLEDの点滅処理を行う。 このプログラムを-O2最適化オプションでコンパイルして得られたサイズは命令部が1992Byte、データ部(固定値)が24Byteだった。
実機で動かす前にシミュレーションで確認してみる。
※.シミュレーションでは割り込み周期を10usに短縮した。
上図の通りフォアグラウンドは while(1); で無限ループしているので、フォアグラウンド実行時のPC(プログラムカウンタ)は同じ番地を指している。 割り込みを検出すると割り込みベクタアドレス0x00000020をフェッチし、そこから割り込みハンドラに分岐し、処理が終わると割り込み前の番地に戻っている。
実機動作

動作周波数については、ちょっと物足りなさを感じるが全く使い物にならないレベルでは無い。 50MHz以上なら遊びに使うにはまぁまぁ十分ではないかと思う。
プログラム開発は、PC上でCでプログラムを書いてコンパイルして動かすのと同等の手順で出来る。
出来上がったマシン語列は今回のように実機にダウンロードして動かすようにも出来るし、ROM化(命令メモリに初期値として設定)することも出来る。難点はデバッグ用の仕組みをまだ入れていないことだが、今の所特に支障は無い。

このCPUでモーターとかアクチュエータを動かしてみたり、装置のユーザーインタフェース部の処理をさせたりしてみたいもんだ。


ERROR: Failed to spawn fakeroot worker to run ...

なにかと忙しくてなかなか趣味の時間を確保できない。 ...orz  家の開発機のOSはLinux Mintなのだが、最近バージョンを22に更新したところ、myCNC用のpetalinuxをビルドできなくなってしまった。ビルドの途中で ERROR: Failed to spawn ...