勉強しないとな~blog

ちゃんと勉強せねば…な電気設計エンジニアです。

マイコンと加速度センサでタコメータを作る - 4. 実データ解析2

RXタコメータをバイクに乗せて比較的長距離走ってみたので、これを解析します。

変更点

いくつか変更したことがあります。

加速度ログデータフォーマットの変更

加速度ログデータの先頭に設定値を残すよう、プログラムの変更を行っています。これで、データから自動で測定間隔、測定分解能を取得してグラフの軸に反映させることができます。

データファイルフォーマット :

アドレス(byte) サイズ(byte) 内容
0x0 0x4 ヘッダ識別子 'LOGH' (0x4C4F4748)
0x4 0x4 ヘッダバージョン 0x1
0x8 0x4 ヘッダデータサイズ(ヘッダ識別子、バージョンは含まない) 0x8
0xC 0x4 測定レート ADXL345レジスタに設定する4bitコード
0x10 0x4 測定レンジ ADXL345レジスタに設定する2bitコード
0x14以降 加速度データ
  • ヘッダ識別子を入れることで、この情報領域を持っていないバージョンのデータでも同じ解析ツールで解析することができます。
  • ヘッダバージョンを入れることで、またあとでフォーマットを変えたくなった場合でも後方互換の解析ツールを用意することができます。
  • ヘッダデータサイズを入れることで、ヘッダの読み飛ばしが簡単にできるようになります。

グラフ表示

横軸を秒単位で、縦軸をG単位(重力加速度単位)にするようにしました。先ほどのヘッダから情報を取得します。

これを反映して、ログファイル読み込みのOctaveスクリプトを変更しました。

function [gData ovrn] = read_adxl345_log_bin_2byte(fname)

  fid = fopen(fname, "r");

  # Read header
  hdrId = fread(fid, 1, "uint32");
  
  if (hdrId == 0x4C4F4748) # 'LOGH', little endian
    hdrVer = fread(fid, 1, "uint32");
    hdrSize = fread(fid, 1, "uint32");
    
    printf("Header detected, Ver:%d, Size:%d\n", hdrVer, hdrSize);

    if(hdrVer == 1)
      hdrRate = fread(fid, 1, "uint32");
      hdrRange = fread(fid, 1, "uint32");
      
      rateCodes = [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, ...
                   0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF];
      rateVals = [0.1, 0.2, 0.39, 0.78, 1.56, 3.13, 6.5, 12.5, ...
                  25, 50, 100, 200, 400, 800, 1600, 3200]; # Measure frequency in Hz
      
      rangeCodes = [0x0, 0x1, 0x2, 0x3];
      rangeVals = [2.0, 4.0, 8.0, 16.0]; # Measure range in G

      rate = rateVals(find(rateCodes == hdrRate)(1));
      range = rangeVals(find(rangeCodes == hdrRange)(1));
      
      dt = 1 / rate;
      gUnit = range * 2 / 1024.0; # 10bit ADC, -range ~ +range
      
      printf("Rate:%d[Hz], Range:%d[G]\n", rate, range);
      
    endif
    
    dataTop = 12 + hdrSize;
    
  else # Header was not detected
    dt = 1;
    gUnit = 0.004; # 4mg/LSB
    fseek(fid, 0, SEEK_SET);
    dataTop = 0;
    
    printf("Header was not detected\n");
  endif

  # Data body
  gData = fread(fid, Inf, "int16", 2);
  gData = [dt*[1:size(gData)(1)].', gUnit*gData];
    # gData(:,1) : Time
    # gData(:,2) : Acceleration
  fseek(fid, dataTop + 2, SEEK_SET);
  ovrn = fread(fid, Inf, "int16", 2);
  fclose(fid);
endfunction

今までのスクリプトでは、読み出し結果として返すgDataは単に加速度データ列でしたが、このスクリプトでは秒単位の時刻列も追加しています。
また、加速度の単位もGに直しています。

# gData(:,1) : Time
# gData(:,2) : Acceleration

実データでグラフ表示

Octaveグラフ表示の手順は以下の通りです。

まずバイナリファイル読み込み。
読み取ったヘッダ情報等も表示されます。

>> [gData ovrn] = read_adxl345_log_bin_2byte("adxl345_log_003.bin");
Header detected, Ver:1, Size:8
Rate:1600[Hz], Range:2[G]

以下のようにしてやると、書き込みバッファのオーバーランが起こったかどうか、どこで起こったかが分かります。

 >> find(ovrn == 1)
ans =

    3196
    3197
    6396
    6397
   17532
   17533
   28668
   28669
   39804
   39805
   41468
   41469
   52604
   52605
   63740
   63741

やっぱりオーバランしているようです。
それほど頻繁にデータが抜けているわけではないので、これで進めます。

次に、前に作ったスクリプトFFT時系列データを作ります。

>> gFFT = fft_slice(gData(1:end,2),512);

OctaveGUIを使っていると、変数の状態を見ることができます。

f:id:nokixa:20200906145240p:plain

"ワークスペースウィンドウ"を見ると、gFFTが512x214のサイズで確保されたことが分かります。
時間方向に214点取れたようです。

前と同様にpcolorを使用してグラフ表示しますが、今回は軸の目盛りで縦軸に周波数の値、横軸に秒単位の時間と、希望の値を表示するようにしたいと思います。

まず、横軸の目盛りを示す配列を用意します。

>> x = gData(512:512:end,1);
>> size(x)
ans =

   124     1

512点のFFTを取っているので、gDataの時間データを1/512に間引いたものとしています。サイズも合っています。

次に縦軸、これは時間刻みの逆数を最大周波数として、これを512分割したものになります。

>> y = [1:512]/512/gData(1,1);

これらを使って、pcolorでグラフ表示します。

>> pcolor(x, y, abs(gFFT));

f:id:nokixa:20200906162555p:plain

横軸、縦軸の目盛りが前回と変わりました。
ラベルも付けておきます。

>> xlabel("t[s]");
>> ylabel("Freq[Hz]");

f:id:nokixa:20200906162932p:plain

周波数範囲後半の半分は見る意味がないのと、DC成分は省きたい、前回見たところではそれほど広い周波数範囲は見ないで良さそうだった、というところから、表示範囲を変えます。
pcolorをやり直すと、ラベル等がリセットされるので、もう一度出します。ついでに表示色凡例も出します。

>> pcolor(x, y(3:128), abs(gFFT(3:128,:)));
>> xlabel("t[s]");
>> ylabel("Freq[Hz]");
>> colorbar

f:id:nokixa:20200906163547p:plain

それらしいものが表示されました。
40秒間くらいのデータですが、確か停止状態→発進→停止のログだったかと思います。
ドラッグスター250は5速までありますが、このログを見るとギアチェンジしたと思われる箇所(回転数が急激に落ちるところ)が4か所あるので、それっぽいです。
いい感じのグラフ表示ができました。

このグラフを見ると、はっきりした周波数のピークが見られるので、このピーク周波数が回転数に対応すると思ってよさそうです。

別データ

前のデータは40秒程度のデータですが、より長時間取ったデータをグラフ表示しました。

f:id:nokixa:20200906170541p:plain

6分ぐらいのログデータになっています。
結構汚くなっているのと、なぜか値の範囲を変更しないと波形が見えませんでした…
うまくグラフが見えなかったので、凡例も消しました。

>> caxis([0 20]);
>> colorbar("off");

これはまた後で表示を見直してみようかな。

まとめ

グラフ表示ができ、どんな加速度が測定できているかという様子が見えてきました。

ここから回転数の推定をしていこうと思いますが、1つ課題として、これが正しいのかどうかを評価する、ということがあります。
この評価方法も考えていこうと思います。

AWS動かしてみる

去年Amazon AWSに登録してEC2を使ってみようとしたのですが、何もしないまま1年近く経ち、無料期間が終わってしまいそうです。
なのでとりあえず動かしてみようと思います。

きっかけ

機械学習の勉強を知り合いとやっていて、GPUが使える環境を用意する方法の1つとして、Amazon AWSを使う、というのがありました。なので登録しました。
1年の無料期間があるのですが、もうすぐ終了になります…

参考

ここのサイトを参考に進めていきます。
だいたいはここに書いてある通りなので、やったこと全部は記載しません。

AWS EC2で仮想サーバを構築!【触って覚えるAWS入門】|日商エレクトロニクス×デジタルトランスフォーメーション

実践

ログイン

もう登録はしてあるので、ログインします。
"アカウント" → "AWSマネジメントコンソール" からログインできました。
"コンソールにサインイン"でもいいと思います。

クラウドならアマゾン ウェブ サービス 【AWS 公式】

f:id:nokixa:20200830005759p:plain
AWSトップページ

最初のメニュー

ログインすると、"仮想マシンを起動する"というメニューがあったので、これをやってみます。

f:id:nokixa:20200830010020p:plain

手順1. マシンイメージ選択

まずはインスタンスのソフトウェア構成を選ぶようです。 今回は、一番上に来ている

Amazon Linux 2 AMI (HVM), SSD Volume Type

を選んでおきます。これが一番標準的なものなのかなと。

f:id:nokixa:20200830014140j:plain

手順2. インスタンスタイプの選択

この手順では、ハードウェア構成が選択できるようです。
元々はGPUが使える環境を用意したかったのですが、"無料利用枠の対象"と書かれているのが"t2.micro"というものだけだったので、これを選びます。
最後に"次のステップ: インスタンスの詳細の設定"をクリックします。

f:id:nokixa:20200830011136p:plain

手順3. インスタンスの詳細の設定

ここはそのままにしておきます。
"次のステップ: ストレージの追加"に行きます。

f:id:nokixa:20200830011843p:plain

手順4. ストレージの追加

"新しいボリュームの追加"を押したら追加できましたが、今回は元に戻して次に進みます。

f:id:nokixa:20200830012236p:plain

手順5. タグの追加

タグを追加するとどうなるのかわかりませんが、手順に従って追加します。
"クリックしてNameタグを追加します"をクリックして、Nameタグを追加しておきます。

f:id:nokixa:20200830012615p:plain

タグの値は適当。

f:id:nokixa:20200830012810p:plain

手順6. セキュリティグループの設定

そのままにしておきます。
セキュリティ的にはおすすめの設定ではないようです。
最後に"確認と作成"でやっと完了になりそうです。

f:id:nokixa:20200830013021p:plain

手順7. 確認

確認します。
"起動"をクリックします。

f:id:nokixa:20200830013407p:plain

手順8. キーペアの作成

"新しいキーペアの作成"を選び、キーペア名を適当に入力します。
"キーペアのダウンロード"でダウンロードを行います。 ダウンロードしたら、"インスタンスの作成"をクリックします。

f:id:nokixa:20200830014335p:plain

インスタンスが作成された!

f:id:nokixa:20200830014816p:plain

赤枠の部分をクリックすると、インスタンスの状態一覧が確認できました。 "インスタンスの状態"が"running"なので、もう使えそうです。

f:id:nokixa:20200830015020p:plain

右下のほうから、パブリックIPをコピーします。
これで、このインスタンスSSHでログインを行います。

SSHログイン

TeraTermを使ってSSHログインをします。 "新しい接続"で、"ホスト"のところに先ほどコピーしたものを貼り付けます。

f:id:nokixa:20200830015350p:plain

"OK"をクリックすると、この画面が出ました。続行します。

f:id:nokixa:20200830015633p:plain

ユーザ名を適当に入力して、"RSA/DSA/ECDSA/ED25519鍵を使う"のところでダウンロードしたキーペアファイルを選択します。

f:id:nokixa:20200830015827p:plain

なぜか一回うまくいきませんでしたが、もう一度TeraTermを起動しなおすとログインできました。

f:id:nokixa:20200830020241p:plain

とりあえず中身を見たりしてみます。

f:id:nokixa:20200830021143p:plain

実際にはここから必要なものをインストールしたりしていくのかと思います。
今回はもう削除してしまいます。

インスタンス削除

管理画面からインスタンスを右クリック、"インスタンスの状態" → "終了"とします。

f:id:nokixa:20200830021636p:plain

参考サイトでは"はい、削除する"という表示だったのですが、こちらではそうなりませんでした…

f:id:nokixa:20200830021815p:plain

"はい、終了する"で以下のような画面になりました。
消えてはいないのでしょうか。
とりあえず止まっているのでよしとします。

f:id:nokixa:20200830022400p:plain

まとめ

今回はインスタンスを立ち上げるだけでしたが、構成の選択やその他色々と設定ができるのが確認できました。
FPGAが使えるインスタンスも用意できるようです。
Amazon AWSのサイトでもアプリケーションチュートリアルがまだありそうなので、気が向いたらやってみようと思います。(そのころには無料期間は終了しているかと)

マイコンと加速度センサでタコメータを作る - 3. マイコンプログラム仕様

一旦マイコンプログラムの説明をしたいと思います。

ソースコード

ソースコードはここに上げています。

GitHub - hubnoki/RXtacho: Designing tachometer using RX220

開発環境

Renesas RXファミリについて詳しくはこちらから。

RX ファミリ | ルネサス エレクトロニクス

あまり詳しくないのですが、Rensas社のミドルレンジのマイコンシリーズです。ARMのCPUコアを使用したマイコンは各社から出ていますが、RXマイコンは独自の設計になっているようです。

ハードウェア

  • 秋月電子製 RX220マイコンボード (通販コード K-08769)
  • 加速度センサ、USBシリアル変換ボード、グラフィック液晶、SDカード(液晶モジュールの裏にソケット実装)、プッシュスイッチを接続
  • 電源 : USBバスパワー給電、5V
    • リニアレギュレータで3.3Vにして使用
    • RX220マイコンボードは5V入力と書かれているが、3.3Vでも動作した(RS232C変換部で改造が必要)
  • USBシリアル変換モジュールでPCと接続、ここから書き込みを実行
    • RX220ボード上のDIPスイッチを切り替え、リセットボタンを押すと書き込み準備完了
    • Renesas Flash Programmerで書き込み
  • プッシュスイッチでユーザ操作
    • モード切り替え
    • 加速度ログ取得の開始/停止操作

回路図をもう一度掲載します。

f:id:nokixa:20200823132637p:plain

とりあえず

今回はここまでにしておきます。
コードの詳細も少しずつ紹介していきたいと思います。

マイコンと加速度センサでタコメータを作る - 2. 実データ解析

加速度データをSDカードに記録できるようになっているので、実際にバイクに乗せてデータを取ってみました。

名前を付けておく

呼び方がないと不便なので、今回作っている工作物の名前は「RXタコメータ」にします。

いきなり結果

こんな感じでデータが取れました。

  • 家の近所を一周してきました。
  • RXタコメータで取得した加速度時系列データ(加速度ログ)を時間ごとにFFTしたものです。
  • 縦の軸が周波数、横の軸が時間
    • 縦軸 : 3.125Hz / 1刻み
    • 横軸 : 0.32秒 / 1刻み
  • ピークの周波数位置がそれらしく動いているように見えます。
    • 最初は1速で発進します。(ギア比最低)
    • 発進した後、ある程度スピードが出たら1段ずつギアシフトしていきます。このとき、アクセルを一旦緩めます。
    • ピーク周波数が急激に落ちているところはおそらくそのときのものです。
    • 色が全体的に薄くなっている箇所は、停止しているときのものと思われます。

何をやったか

Octaveを使って、加速度時系列データから先ほどのグラフを作成しました。
MATLAB互換のフリーのツールです。

www.gnu.org

他にもpython + numpy + matplotlibでも同じようなことができそうです。

具体的にやったこと

先ほどのグラフ作成の手順は以下の通りです。

  • SDカードをPCに差して、RXタコメータで取得した加速度ログをコピー
  • Octaveで、加速度ログ(バイナリデータ)を読み込む
  • 加速度ログを適当な長さ(今回は512にしました)に区切って、それぞれFFTを実施
    • 「スペクトル強度(周波数, 時刻)」の形のデータができる
    • 周波数点数 : 512、ただし元が実数データなので半分は意味のないデータ
    • 時刻点数 : 時系列データ点数/512
  • Octaveの2次元プロット機能でグラフ表示
    • FFT結果のスペクトルは複素数で出てきますが、今回は位相は見る必要はないので、振幅だけプロットしました。

Octaveコード

加速度データ取り込み

以下のファイルを作って、read_adxl345_log_bin_2byte()関数で加速度データを取り込めるようにしました。

function [dat_g dat_ovrn] = read_adxl345_log_bin_2byte(fname)
  fid = fopen(fname, "r");
  dat_g = fread(fid, Inf, "int16", 2);
  fseek(fid, 2, SEEK_SET);
  dat_ovrn = fread(fid, Inf, "int16", 2);
  fclose(fid);
endfunction

加速度ログファイルには、以下のようにデータが並んでいます。

G[0] FLG[0] G[1] FLG[1] G[2] FLG[2]  ...

G[i] : i番目の加速度データ(16bit)
FLG[i] : i番目データの関連フラグ等(16bit)

Octaveコードのfread()では、バイナリデータを繰り返し読んで行列変数に格納する、ということが可能です。

上記で使ったfread()のフォーマット :

val = fread(fid, size, precision, skip);
# fid : ファイルID  
# size : 読み出しデータ数、Inf -> 読めるだけ読む  
# precision : 読み出しデータの型(uint8, ushortなど)
# skip : 繰り返し読み出しの際の読み飛ばし数(バイト単位)

"関連フラグ等"としては、現状"オーバランフラグ"を入れています。
今回使っている加速度センサは、決まったレートで勝手に測定を行い、これを割り込みピンでCPUに通知します。
CPUでは、SDカードの1セクタ分データがたまったら実際にSDカードへの書き込みを行いますが、これがそれなりに時間がかかってしまうので、この間に入ってくる加速度データを取りこぼしてしまうことがあります。
"オーバランフラグ"は、この取りこぼしが発生したことを示すものです。

FFT

以下のファイルを用意して、所定のデータ数ごとにFFTを行いました。
窓関数もかけています。

function ffts = fft_slice(d, u)
  # d : data for FFT
  # u : unit size for FFT
  
  n_ffts = cast(floor(size(d)/u), "uint32");
  for i = 1:n_ffts
#    ffts(:,i) = fft(d(1+(i-1)*u : i*u));
    ffts(:,i) = fft(d(1+(i-1)*u : i*u) .* hamming(u));
  endfor
  
endfunction

グラフ表示

今回のような2変数のデータを可視化するのに、いくつかのグラフが用意されています。

  • contour (contourf)
  • surf
  • pcolor

全部試してみたのですが、pcolorが一番わかりやすく表示されました。これが記事先頭のグラフです。
グラフ表示した際のコードは以下の通りです。

[gData ovrn] = read_adxl345_log_bin_2byte("adxl345_log_002.bin");
gFFT = fft_slice(gData, 512);
pcolor(abs(gFFT(3:128,:)));
colorbar
caxis([0 4000])
  • FFT結果の絶対値をpcolorで表示しています。
  • FFT結果の低周波数領域は表示から除外しています。(DC成分とDC成分が窓関数で広がったと思われる部分)
  • 加速度軸のレンジを狭く調整しています。

グラフ表示参考 :

Yapso, Yet Another Plotting System for Octave

改善項目

  • 軸の目盛りを秒単位、Hz単位にしたい
  • 加速度値もきちんとしたい
    • 今は16bitのAD変換値をそのまま使っています。加速度センサ(ADXL345)は、今回使った設定だと4mg/LSBのAD変換になっています。
  • いずれもできそうなので、次回やってみます。

困ったこと

いくつか困ったことはありました。

  • 取得した加速度ログデータを見ると、最大レートの3200Hzではオーバランが発生していました。1600Hzでもやはり発生していました。
  • SDカードの接続が怪しいのか、ファイルオープンや書き込みに失敗することがありました。RXタコメータの上部をちょっと開けるとちゃんと動いたりしました…
    ハードを作り直したい…

その他

はてなブログではコードブロックでのファイル名表示ができなかったのですが、以下のサイトのデザインCSSを使わせていただいて、表示できるようにしました。

はてなブログでソースコード表示時にタイトルを付ける - 酔いどれ技術者の備忘録


次回

もうちょっと長距離走って測定を行ったので、その結果を見てみたいと思います。

マイコンと加速度センサでタコメータを作る - 1. 現状

マイコン工作でバイク用のタコメータを作ろうとしています。

きっかけ

YAMAHA ドラッグスター250に乗っています。
このバイクにはタコメータ(エンジンの回転数メータ)が付いていません。
ギター用のチューナーで、ギターにクリップで挟んでその振動を拾うようなものがあり、似たようなことをすれば配線を引いたりせずとも(乗っけるだけで)タコメータを用意できるのではと思い、始めました。

方式

バイクに加速度センサを乗せて、その周波数成分から回転数が推定できるのではと考えました。
単純にピーク周波数を使うだけでは難しいかと思っていますが、前後の時間のデータを使うとか、なんとか工夫でうまくできないかと。
このあたりが面白そうポイントです。

構成

構成はこんな感じ。

  • Renesas社RXマイコンを使用
    • 秋月で小さいボードが販売されていて、今までにも使ったこともあるので。
  • 秋月の加速度センサモジュールを使用
    • SPIインターフェースでマイコンと接続
    • 最大3200Hzで加速度サンプリング可能
    • バイクの回転数を測定できるだけのレートが必須なので、どれくらいのレートで測定できるかは重要。
  • aitendoのグラフィック液晶モジュールに回転数を表示
  • SDカードを搭載して、回転数推定アルゴリズム検証用の加速度データを記録
  • ユーザ操作用の押しボタン
  • USBで給電
    • バイクにはUSBポートを用意しているので、ここから給電します。

回路図はEagleで書きました。

RXtacho回路図

主な部品

リファレンス 品名 メーカー 備考
U1 RX220マイコンボード 秋月電子 RX232Cレベルコンバータの接続を改造する
U2 FT234X 超小型USBシリアル変換モジュール 秋月電子 ポリスイッチを350mA品に交換する
U3 3軸加速度センサーモジュール 秋月電子 搭載IC : Analog Devices社 ADXL345
SV1 1.8インチ液晶モジュール(SPI)[M018C7735S] aitendo 14ピンピンヘッダをメイン基板上に実装、液晶モジュールには14ピンリボンケーブルを半分に切って半田付け
裏にSDカードコネクタあり

ついでに、実装はユニバーサル基板上に手配線ですが、Eagleで検討。

RXtacho基板配線図

今のところ、

  • ハードは作成済み
    • タカチのプラスチックケースに詰め込みました。
  • RXマイコンコンパイル、書き込み環境は整っている

  • 各機能(UART、SD、グラフィックLCD、加速度センサ、プッシュスイッチ)は使えるようになった

  • ファイルシステムも入ってます!
    SDカードのファイル読み書きができます。 ChaN氏のFatFsを使わせていただきました。
    FatFs 汎用FATファイルシステム・モジュール

  • UARTから一通りの機能確認はできる

  • FFT導入した!
    大浦氏のライブラリを使わせていただいて、少し改造(固定小数点演算化)しました。
    General Purpose FFT (Fast Fourier/Cosine/Sine Transform) Package

  • アプリケーションをある程度実装しました。

    • モードを4つ用意、プッシュスイッチで切り替え
    • 加速度ログ取りモード
      加速度センサーのデータをSDカードに記録するモードです。
    • FFTモード
      加速度データをFFTして、グラフに表示するモードです。
      FFT結果をSDカードに残すこともできます。
    • 設定モード
      加速度のデータレートなど、設定項目がいくつかあるので、それらを設定
    • メーターモード
      これが最終目標。
      まだできてません(^ω^)

実物

こんな感じでできています。

ぱか。中身。

以上

今回はここまでにします。

ZYBOを進める - 24. カメラモジュールを動かす

今回から、カメラモジュールを動かして画像を取り込めるようにしていきたいと思います。

このカメラモジュール自体はかなり前々からあるもののようで、2011年くらいにこれを使ったFPGAデザインの記事があったりします。
かなり今更感…
とりあえずこのあたりのサイトも参考にしながら進めていきたいと思います。

FPGAの部屋 OV9655を使ってみる1(購入、変換基板設計)

OV9655で作るAXI USB3.0カメラ: なひたふJTAG日記

カメラモジュールの仕様

まず、今回のカメラモジュールの仕様を確認します。

概要

このモジュールは、2.54mmピッチピンヘッダとOmni Vision社のOV9655を搭載しています。
(aitendoのサイトからデータシートをダウンロードすると、OV9656と書いてあるが…?)

f:id:nokixa:20200524192306p:plain

一応、こちらからOV9655のデータシートをダウンロードできました。以下では、このデータシートの内容を参照します。

http://www.arducam.com/downloads/modules/OV9655/ov9655_full.pdf

OV9655は、以下のような仕様のCMOSイメージセンサです。

  • 解像度 SXGA (1280x1024)、その他解像度も設定可能
  • SXGA解像度で15fps
  • 露出制御、ガンマ補正、ホワイトバランス、といった画像処理機能を内蔵
  • SCCBインターフェースで各種設定が可能

IO

上の写真にもありますが、以下のようなIOピンがあります。 モジュール基板上の接続は、aitendoサイトから回路図を参照。

ポート名 機能
3.3V 電源
GND グランド
SIOC SCCBインターフェース : クロック
SOID SCCBインターフェース : データ
VSYNC 垂直同期信号
HREF 水平同期信号
PCLK 出力データクロック
明記はないが、XCLKが遅延してほぼそのまま出てくるよう
XCLK 入力クロック
10MHz~48MHz (標準24MHz)を入力する
D9~D2 出力データ (8bit)
OV9655自体は10bitのデータ出力ポートを持っていますが、このモジュールでは上の8bitだけ取り出されています。
RET リセット信号
OV9655のRESETに接続されている
回路図を見る限り、Low-active (Lでリセット、Hで通常動作)
PWDN パワーダウン (Lで通常動作、Hでパワーダウンモード)

SCCBインターフェース

カメラの設定を行うSCCBインターフェースについては、以下に仕様が記載されています。

http://www4.cs.umanitoba.ca/~jacky/Teaching/Courses/74.795-LocalVision/ReadingList/ov-sccb.pdf

特にこの中での言及はありませんが、IICインターフェースと互換の仕様となっています。

レジスタ書き込みの場合は、以下のように、スレーブID→レジスタアドレス→レジスタデータ とすればいいようです。

f:id:nokixa:20200525011326p:plain

ついでに、ACタイミング特性も。

f:id:nokixa:20200525011719p:plain

f:id:nokixa:20200525011924p:plain

画像データ信号タイミング

基本的に、PCLKを基準としてデータが出てきます。

f:id:nokixa:20200525012049p:plain f:id:nokixa:20200525012543p:plain

  • PCLKの立ち下りでD9~D2にデータが出てくる、これをPCLKの立ち上がりで取り込めばよい
  • 1ラインデータ期間はHREFがHになる、HREFもPCLKの立ち下がりで出てくる
  • 1フレームの頭でVSYNCが数ライン期間Hになる(解像度により、VSYNCからのオーバヘッドやHREFの間隔が異なる)

D9~D2の中には、以下のようにピクセルデータが入ります。
2クロックで1ピクセル分になります。
ここで示しているのはRGB565フォーマットですが、他にもRGB555、YUVフォーマットで出力可能です。

f:id:nokixa:20200525081505p:plain

ACタイミング特性は以下の通りです。

f:id:nokixa:20200525081114p:plain

FPGA構成

FPGAは、以下のように構成していこうと思います。

  • SCCBインターフェース用にI2Cインターフェースを用意します。(互換なので)
    ただし、今まで使っているベースデザインでは、2つあるPS部のI2Cポートを両方使ってしまっています。
    なので、PL部にXilinx製のIPを追加してこれで制御しようと思います。
    f:id:nokixa:20200528002459p:plain
  • カメラモジュールからの画像データを受け取ってDDR3メモリに書き込むIPを自作し、これをブロックデザインに追加します。
    前回のHDMI出力デザインも、DDR3メモリに一度テストパターンデータを書き込み、これを読み出して表示していました。
    これでカメラ画像をHDMI表示することができると思います。 f:id:nokixa:20200528014327p:plain

コーディング

では、実際にデザインを作っていきます。

ブロックデザイン

まず、ブロックデザインにAXI IIC IPを追加します。
Vivadoでブロックデザインを開いて、ブロックデザイン上で右クリックして"Add IP"を選択します。
ブロックデザインウィンドウ上部の"+"ボタンからでも可能です。

f:id:nokixa:20200528081140p:plain

すると、IPの選択ウィンドウが現れます。この中に、使えるIPがいろいろ入っているので、見てみるのも一興です。

f:id:nokixa:20200528081407p:plain

今回は、検索バーに"iic"と入力します。"AXI IIC"が見つかるので、Enterキーを押します。すると、ブロックデザインにIPが追加されます。
また、ここでも前回のようなDesigner Assistance機能が使えるので、使います。

f:id:nokixa:20200529080613g:plain

DesignerAssistanceでは、追加したIPのポートをどうつなげるか、を自動でやってくれます。
が、どういう接続をするか確認します。

まず、"IIC"を選びます。Descriptionを見てみると、"IPのインターフェースをボードのインターフェースに接続する"とあります。
デフォルトの設定では、HDMIDDCポートにつながるようになっていたようでした。
今回は、"Custom"を選択します。 選択すると、"IIC"のチェックボックスに自動でチェックが入りました。入らなかったら、手動でチェックが必要です。

f:id:nokixa:20200529081541g:plain

次に、"S_AXI"の設定です。 デフォルトの設定では、"/processing_system7_0_axi_periph"に接続するとなっています。 今回は選択はできませんが、複数のInterconnect IPがある場合は、選択ができます。
クロックソースの選択はありますが、今回は"Auto"のままにします。
最後に、"S_AXI"のチェックボックスに手動でチェックを入れて、"OK"をクリックします。

f:id:nokixa:20200529081639p:plain

すると、IPのポート接続が行われます。

f:id:nokixa:20200530212424p:plain

Address Editorを見ると、processing_system7_0 (PS部ARMコア)の下にAXI IICが追加されていることが分かります。

f:id:nokixa:20200530212644p:plain

この段階ではまだ変更は保存されていないので、Ctrl+sで上書き保存しておきます。

カメラデータ受信部

次に、OV9655からのデータを受け取ってDDR3に書き込むロジックを作成します。
ここまでまともにRTLを書いてきませんでしたが、やっとRTLコーディングです。私はVerilogを使って来ているので今回もVerilogで書きます。 SystemVerilogも使ってみようかな...

このロジックは、以下のように構成します。

  • FIFO形式のバッファを用意
    • 入力データは、OV9655からのPCLKに同期して送られる
    • データを取り出して、AXI4 MasterインターフェースでDDR3へ転送する
  • OV9655からのデータおよびHREF、VSYNCをFIFOへ書き込み
    • 書き込みのクロックはPCLK
    • XCLKに24MHzを入力することとする → PCLKも24MHz
    • 毎クロック書き込みを行う
      HREF、VSYNCを見て書き込みをしてもいいですが、その場合、PCLKドメインからm_axi_aclkドメインへの非同期クロック間パスを処理しないといけないので、もう全部FIFOで非同期部は済ましたほうがすっきりするかな、という考えです。
  • FIFOからこれを読み出し、AXI4 MasterインターフェースでDDR3へ書き込む
    • 読み出しのクロックは任意に設定できる(AXI interconnectの設定による)が、axi_mem_interconに接続されている他のポートとそろえることとする
    • このクロックはprocessing_system7_0のFCLK_CLK1に接続されていて、140MHzとなっている
    • 読み出しデータからHREF、VSYNCを取り出し、データの有効性およびフレーム区切り、ライン区切りを確認する
  • AXI4 Slave Liteインターフェースを用意し、これをCPUに接続して、CPUから画像データ取得の制御を行う

f:id:nokixa:20200531022304p:plain

DDR3に格納した画像データをHDMI出力できるように、前回のHDMI出力デザインのときのフォーマットに合わせて画像の格納を行う

HDMI出力デザインのCPUコードで、テストパターン生成の処理を見ると、どのように画像データを用意すればいいかがわかります。

ピクセルデータのフォーマットは、以下のようになっているようでした。4byteの中に1pixel分のデータが入っていて、1byteは空きになっているようでした。

f:id:nokixa:20200531020856p:plain

また、1フレームのデータは以下のように配置されています。
水平方向から先に出ます。

f:id:nokixa:20200601012255p:plain

IP作成

今回は、上記のロジックを、ブロックデザインに追加するIPの形で作成します。
Vivadoで、"Tools"メニューを開くと、"Create and Package New IP"というのがあるので、これを選択します。

f:id:nokixa:20200604024539p:plain

"Create and Package New IP"のウィザードが立ち上がります。
Nextをクリックします。

f:id:nokixa:20200604080718p:plain

ここでは、AXI4インターフェースを持ったIPを新規で作成するので、"Create AXI4 Peripheral"にチェックを入れて、Nextをクリックします。

f:id:nokixa:20200604080928p:plain

IPの名前、情報を設定します。
IPの置き場も設定します。元々Digilentリポジトリからクローンしてきた段階で、ip_repoディレクトリが用意されていたので、ここをIP置き場にします。

f:id:nokixa:20200604081105p:plain

IP外部インターフェースの設定画面です。
まずAXI Lite Slaveポートが設定されています。これは、今回CPUからの設定用ポートとして使うこととします。

f:id:nokixa:20200604081311p:plain

+ボタンをクリックすると、AXIポートを追加することができます。

f:id:nokixa:20200604081810p:plain

ここでは、画像データを出力するAXI Masterポートを追加したいので、追加したポートの設定を以下のように変更します。

  • Name : M00_AXI
  • Interface Type : Full
    バーストアクセスが必要の場合は"Full"に設定が必要(今回は必ずしも必要ではない)
  • Interface Mode : Master (必須)

"Interface Mode"の設定は必須です。

f:id:nokixa:20200604081841p:plain

最後にサマリーが表示されます。
"Edit IP"にチェックを入れて、"Finish"をクリックします。
ウィザードが終了して、新しくVivadoが立ち上がります。

f:id:nokixa:20200605081917p:plain

f:id:nokixa:20200605082105p:plain

IP編集

Vivado上で、IPの編集を行います。

と思ったのですが、よく見たらプロジェクトの言語設定がVHDLでしたorz
生成されたテンプレートがVHDLになっています。

f:id:nokixa:20200606142707p:plain

今からVHDLを勉強するのも面倒なので、設定を変えます。
一旦IP編集のVivadoプロジェクトを閉じて、ip_repoフォルダの中身を全部消しておきます。

f:id:nokixa:20200606143205p:plain

元のプロジェクトに戻って、Project Summaryを見ると、Target languageがVHDLになっているので、これを変更します。

f:id:nokixa:20200606143249p:plain

VHDLのところをクリックすると、プロジェクトの設定ウィンドウが出てきます。Target languageをVerilogに変更します。

f:id:nokixa:20200606143657p:plain

インプリメント結果を残しておくか、と聞かれましたが、不要なのでNoとします。

f:id:nokixa:20200606143715p:plain

この後、先ほどと同じ手順でIP編集まで行くと、無事Verilogでテンプレートが出てきました。

f:id:nokixa:20200606145017p:plain

とりあえずここまで

長くなったので、いったんここで区切ります。
次回はVerilogのコーディングからになります。

ZYBOを進める - 23. HDMI制御続き

引き続きZYBOでのHDMI出力のデバッグです。

方針

実際に動作しているときの様子を見てみるのが手っ取り早いかと思うので、PS部(CPU)とPL部(FPGA)のそれぞれでデバッガでの動作チェックをしてみたいと思います。

PL部(FPGA)のデバッグ

XilinxFPGAでは、ILA(Integrated Logic Analyzer)が便利です。
これは、FPGAロジック内部の信号を動作中にモニタする機能で、合成またはインプリメントの前に観測したい信号を設定しておきます。

今回気になるのは、

  1. ちゃんと画像データが流れているのか?
  2. 画像出力のタイミング信号がちゃんと出ているのか?

といったところ。
これらを見てみようと思います。

ILAの設定

まずVivadoを開いて、ブロックデザインを開いてみます。
axi_vdma_0(AXI Video Direct Memory Access)のM_AXIS_MM2Sポートにつながっているネットを選択します。このネットは、PS部のDDRメモリから取り出した画像データを画像出力パスに流すAXI4 Streamインターフェースになっています。

f:id:nokixa:20200402025552p:plain

このネットを右クリックすると、"Debug"のメニューが出ます。これを選択すると、デバッグするネットとして指定されます。

f:id:nokixa:20200402031453p:plain

そうすると、"Designer Assistance available"と出るので、ありがたく使わせてもらいます。

f:id:nokixa:20200402033123p:plain

"Run Connection Automation"のダイアログでは、そのままOKします。

f:id:nokixa:20200402034336p:plain

すると、ブロックデザインにILAコアが追加されます。

f:id:nokixa:20200402035037p:plain

同様にして、v_axi4s_vid_out_0(AXI4-Stream to Video Out)のvid_outのネットにILAを準備、接続します。ここのネットの中身を見てみると、画像出力のタイミング信号らしきものが含まれていて、様子を見るのによさそうな感じです。(IPブロックのポートの部分の+マークをクリックすると、ポートに含まれる信号が表示されます。)

f:id:nokixa:20200402035523p:plain

ここまでできたら、ブロックデザインを保存(ブロックデザイン上でCtrl+S)して、合成・インプリメント・ビットストリーム生成を行います。

ILAでの波形観測

いつも通り、SDKへのハードウェアエクスポート、FPGAの書き込み、SDKでのプログラムのデバッグ実行を行います。
この後、VivadoのFlow Navigatorから"Open Hardware Manager"をクリックします。

f:id:nokixa:20200402073724p:plain

Hardware Managerが立ち上がるので、"Open target"、"Auto Conect"とクリックします。

f:id:nokixa:20200402074057p:plain

すると、PCに接続されているZYBOのFPGAバイスが自動で検出され、"Hardware"ウィンドウに表示されます。
2つのデバイスが見えますが、"arm_dap_0"というほうがZynqのARMコア、"xc7z010_1"というほうがPL部を表します。

f:id:nokixa:20200402074331p:plain

"Refresh Device"をクリックし、対象のFPGAバイスを選択します。

f:id:nokixa:20200402074535p:plain

すると、上の手順で追加したILAが"Hardware"ウィンドウの中で"hw_ila_1"、"hw_ila_2"として現れます。また、波形ウィンドウも現れます。

f:id:nokixa:20200402075716p:plain

右下の"Trigger Setup"ウィンドウで、波形観測のトリガとなる信号を設定できます。+ボタンを押すと、波形選択が現れるので、選択します。波形ウィンドウからドラッグ&ドロップでも可能です。
ここでは、"TVALID"信号を選択します。

f:id:nokixa:20200403012419p:plain

"Trigger Serup"や"Settings"のウィンドウで、トリガ条件(比較する値、値の一致・不一致など)、トリガ位置(サンプルウィンドウ内でトリガ条件が成立する点の位置)を設定できます。今回は、TVALID==1 がトリガ条件で、サンプルウィンドウの深さは1024、トリガ位置は512(ちょうど真ん中)です。

f:id:nokixa:20200403080438p:plain

矢印ボタンを押すと、トリガ待ち状態になります。 今回は、TVALID信号が常に発生しているようで、すぐにトリガがかかって波形が出てきました。

f:id:nokixa:20200405164626p:plain

とりあえずデータは流れているよう。
もう1つのILAのほうを見てみます。
今回は上のほうのタブに自動的に"hw_ila_2"として出てきています。 もし出ていなかったら、"Hardware"ウィンドウの"hw_ila_2"をダブルクリックすれば波形ウィンドウを出すことができます。
これで適当な信号を選んでトリガ待ちを開始しましたが、トリガがかかりませんでした。("Hardware"ウィンドウで見ると、"Status"が"Waiting For Trigger"となっている)

f:id:nokixa:20200405170759p:plain

v_axi4s_vid_out_0からちゃんとタイミング信号が出ていないということなので、もう少し原因を追ってみます。

ブロックデザイン再確認

ブロックデザインに戻って見てみると、タイミング信号の元になりそうなところがあります。

f:id:nokixa:20200405224057p:plain

v_tc_out(Video Timing Controller)からv_axi4s_vid_out_0に行っている信号が、先ほど見た信号とほぼ同じ内容になっています。これをILAで見てみます。
また、v_axi4s_vid_out_0からv_tc_outのgen_clkenポートに信号が行っています。これもILAでの観測信号に追加します。
"Debug"設定を付けて、Designer Assistance機能でILAを追加しますが、よく見てみると、"v_axis_vid_out_0_vtg_ce"信号の"Source Clock"が"None(Connect manual)"となっています。
今まで見た信号はVivadoのほうで自動で対応するクロックを見つけてくれていましたが、この信号は自分で設定しないといけないようです。

f:id:nokixa:20200405224257p:plain

ブロックデザインを見る限り、v_tc_outへ接続されているクロック信号は"s_axi_aclk"と"clk"で、このうち"s_axi_aclk"はPS部からの制御用AXIポートのクロックに当たるので、もう一方が実際の動作クロックに当たるのではないかと考えられます(本当はちゃんと確認したほうがいいかもしれません。合成した後にVivadoの機能で確認できたりします)。 "clk"ポートに接続されているのは"axi_dynclk_0"の"PXL_CLK_O"ポートなので、これを選びます。
すると、既存の"system_ila_1"にポートが追加されて、そこに接続されました。

f:id:nokixa:20200406004547p:plain

これで再度合成、インプリメント、実機動作をしてみます。

f:id:nokixa:20200406005041p:plain

"system_ila_1"に信号が追加されたので、"hw_ila_2"を開き、+ボタンを押して追加された信号を波形ウィンドウに追加します。
また、トリガ信号に、追加した信号のうち"hsync_1"を設定して見てみると、ここの信号はちゃんと出ているようでした。

f:id:nokixa:20200406013521p:plain

ということは、v_axi4s_vid_out_0(AXI4-Stream to Video Out)の設定か使い方に問題があるのでは、となります。

このIPは、Xilinx製のIPで、Zybo-hdmi-outプロジェクトと現在のプロジェクトで比較してみると、バージョンは同じになっていました。設定も同じよう。

f:id:nokixa:20200406014817p:plainf:id:nokixa:20200406014821p:plain

じゃあ何が違うのか、ということで接続されているものを再確認すると、

f:id:nokixa:20200406015018p:plain

と、AXI4-Stream Subset Converterの設定で"TLAST Remap String"が"1'b0"となっていて、常に0になってしまうような感じでした。
前回見たところでは、

f:id:nokixa:20200406015200p:plain

と、"tlast[0]"となっていました。(Zybo-hdmi-outプロジェクトでも同じ)
設定変更したときに一緒に変更されてしまったのか。
これを修正して再度トライ。

f:id:nokixa:20200406021441p:plain

インプリメント後、確認してみるとCritical Warningが発生して、Project Summary (Flow NavivatorのPROJECT MANAGERをクリックすると出てくる)を見ると、タイミング制約を満たせなかったようです。たぶんILAの信号の追加のし過ぎのため。もしかしたらこの前もそうなっていたかも。
ILAの信号を一部削除して再度インプリメントします。

f:id:nokixa:20200406022101p:plain:w300

一度system_ila_1を削除して再度追加したほうが楽そうです。

f:id:nokixa:20200406022349p:plain

今度Designer Assistanceを使用したときに、接続する信号を選択できるので、最初に接続したものだけ選択します。
これで再度進めます。

f:id:nokixa:20200406025339j:plain

いけました!
信号もちゃんと出ています。

f:id:nokixa:20200406025606p:plain

振り返り

かなり長くなってしまいました。
デバッグの過程をブログに書きながらだとなかなか大変…
デバッグ手順が自分の中で整理できていないからかもしれません。

HDMI制御の詳細も全く追っていないので、進めていったときに困るかも。
まあ必要に応じて見ていきます。

次回

今度からはカメラモジュールを接続して画像を取り込めるようにしたいと思います。