HDDサンダーで 回転数をXIAOで読み取るまでできた。 その際に取得できた ArduinoでXIAOのプログラム、 ライブラリでは解決できず、 自分でチップのレジスタを操作するような プログラムを書くために 必要な知識をいくつか紹介する。
Arduino で XIAOプログラミング電子工作プログラミング
ソースの在り処
seeeduino XIAOのarduionoのソースは web上では https://github.com/Seeed-Studio/ArduinoCore-samd にある。割と見やすいので、よく利用する。 ただし tools以下のファイルは無い。 PC上では、arduinoのboardマネージャで導入した場合, C:\Users\ユーザ名\AppData\Local\Arduino15\packages\Seeeduino ディレクトリ以下に配置される。
Exploreで開けるのも面倒な場所なので C:\Users\ユーザ名\AppData\Local\Arduino15\packages フォルダを exploreで「クイックアクセスにピン留め」しておくと 各種CPUのarduinoのソースに簡単にアクセスでき 便利になる。
ついでに cygwinのchereコマンドで context menuに「Cygwin here」を追加しておくと、 cygwinを開き
$ find . -type f | xargs grep SOME_TEXT $ find . -name '*.h' | xargs grep SOME_TEXT
などとして、任意の文字列の出現箇所を調べられるので便利。
プログラミングの雰囲気を掴む
チップのレジスタを操作するようなプログラムを いきなり書くのは敷居が高いので、 まずは簡単な機能のソースを見て 雰囲気を掴もう。
GPIOの操作の仕方は digitalWrite()のソースを見る。 wiring_digital.c にある。 PWMの使い方は wiring_analog.c のanalogWrite()を見る。
プログラムを詳しく読む
雰囲気だけではプログラムは書けないので、 真面目に XIAOで扱われているマイコン、 ATSAMD21G18に取り組もう。 データシートにざっと目を通す。
マイコンの各ブロックの制御レジスタは 型定義が
C:\Users\ユーザ名\AppData\Local\Arduino15\packages\Seeeduino\tools\CMSIS-Atmel\1.2.1\CMSIS-Atmel\CMSIS\Device\ATMEL\samd21\include\componentにブロックごとに有る。
実態の定義は以下に有る。
C:\Users\ユーザ名\AppData\Local\Arduino15\packages\Seeeduino\tools\CMSIS-Atmel\1.2.1\CMSIS-Atmel\CMSIS\Device\ATMEL\samd21\include\samd21g18a.h
これらを参照しながら読むとプログラムの詳細な動きが 理解できる。
PWMの周期を変更
前回の報告で述べた通り、 HDDモータを制御するPWM信号の周波数は DRV11873の仕様で 7~100kHzの範囲内である 必要がある。 Seeeduino XIAOの analogWrite()のPWMの周波数は 実測で730Hzぐらいなので 仕様に合わない。
これを修正すべく wiring_analog.c を読んだところ以下のことが判明した。
- カウンタは16bitモードで動作
- 48MHzのクロックを216=65536で分周し、 48,000,000Hz ÷ 65536 = 732.421875 Hzが出力されている。
- カウントの最大値 0xffff を設定している箇所がある。
カウントの設定値を4800に変更することで、 PWMの周波数を 48MHz ÷ 4800 = 10KHz にすることができた。
信号の周期を計測
BLDCモータドライバDRV11873は モータ駆動の1サイクルに同期した パルスがFG端子から出力される。 この何サイクルかがモータの1回転に 対応するので、この信号の周期を計測することで モータの回転数を取得することができる。 ATSAMD21G18のカウンタでFG信号の周期を 計測したので、その方法を説明する。
カウンタで信号の周期を計測するための 構成を図に示す。 外部ポートPA4から入力された信号を EIC(外部割込)に接続しイベントを発生させ、 EVSYS(イベントシステム)でタイマーに接続し 周期を計測する。タイマーはTC4とTC5を合わせて 32bitタイマーとして計測する。 クロックはシステムクロックの48Mhzを使用するので 最大 232[count]÷ 48M[count/秒] = 89.47[秒]周期まで計測可能 となる。
特徴的なのがEVSYS(イベントシステム)モジュール。 これは各周辺ブロック間で信号をやり取りするバスで
- 12チャンネル
- イベント・ジェネレータ74個
- イベント・ユーザ29個
EICの使い方は WInterrupt.cにある attachInterrupt()のソースを見ると 大体わかる。
EVSYSは使い方が分かりづらい。 arduinoでは全く使われていない。 ハマったのは以下の2点。
- クロックを供給する必要がある
- レジスタを部分アクセスすると失敗する
クロックの供給というのは、コードで言うと以下の部分。
PM->APBCMASK.bit.EVSYS_ = 1;
STM32とかでプログラムしていると当たり前の部分だが、 Arduinoのソースを読んでいてもあまり出てこないので 気が付かなかった。 調べると wiring.cで結構初期化されている。
Datasheetの 11章 Peripherals Configuration Summaryが参考になった。
レジスタを部分アクセスすると失敗する というのは次の部分。 まず、正常に動かないコード。
EVSYS->USER.bit.CHANNEL = 2; EVSYS->USER.bit.USER = 0x13;
正常に動くコード。
EVSYS->USER.reg = EVSYS_USER_USER(0x13) | EVSYS_USER_CHANNEL(2);
このコードはイベント・チャンネル1 (CHANNEL-1の値)の出力を TC(USER 0x13)に接続しているが、 上側のコードではDMAC CH0(USER 0x00)にも接続されてしまい、 DMAC側でイベントを受け入れる設定になっていないので エラーとなり、イベント・チャンネル1が動かなる模様。 正常に動くコードにたどり着くまで、かなり時間がかかった。
割込処理
これでタイマーに信号の周期が記録されるようになるが、 プログラムの処理上、周期計測後に割込を発生させる。 割込の処理も WInterrupt.cが参考になる。
タイマーのレジスタで割込を有効化し、 以下のコードでNVIC(割り込みコントローラを設定する)
IRQn_Type v = TC4_IRQn; NVIC_DisableIRQ(v); NVIC_ClearPendingIRQ(v); NVIC_SetPriority(v, 0); NVIC_EnableIRQ(v);
cortex_handlers.c にデフォルトの割込ハンドラが定義されている。 TC4関連だけ抜き出すと以下のようになる。
/* Default empty handler */ void Dummy_Handler(void) { #if defined DEBUG __BKPT(3); #endif for (;;) { } } ... void TC4_Handler ( void ) __attribute__ ((weak, alias "Dummy_Handler"))); ...
デフォルトのTC4割込ハンドラは Dummpy_Handlerという無限ループのハンドラが設定されている。 weak属性が付いているので、 TC4_Handlerという 関数を定義すれば呼び出される。
void TC4_Handler(void){ uint8_t flags = TCx->COUNT32.INTFLAG.reg; if (flags & TC_INTFLAG_MC0) { ... 割込処理 ... } // 割込フラグクリア TCx->COUNT32.INTFLAG.reg = 0xff; }
VSCodeのエラーを消す
VSCodeでArduinoのソースを編集していると intellisenseで大量のエラーが表示される。 今までは無視してきたのだが、 今回 エラーを消すべく設定を調べてみた。
intellisenseの設定は プロジェクトのフォルダーの .vscode/c_cpp_properties.json を用意すれば良いのだが、 自分で最初から書くのは大変なので VSCodeにArduino拡張を利用する。 拡張機能タブでArduinoを検索すると 3つぐらい見つかるので、その中の Microsoft製のものをインストールする。
インストール後、コマンドパレットで Arduino:initializeを実行。 Boardで Seeduino XIAOを選択すると .vscode/c_cpp_properties.json が生成されるが、 まだ膨大にエラーが表示される。
修正が必要なのは defines と includePath。 definesの参考になるのが Seeeduino Arduinoの boards.txtファイル。 これに
seeed_XIAO_m0.build.extra_flags= -DARDUINO_SAMD_ZERO -D__SAMD21__ -D__SAMD21G18A__ -DARM_MATH_CM0PLUS -DSEEED_XIAO_M0 {build.usb_flags}という行がある。 あと、足りないincludePathを追加し 以下の設定でエラーが無くなった。
{ "env": { "users": "C:/Users/ユーザ名", "ulib": "${users}/Documents/Arduino/libraries", "seeeduinoDir": "${users}/AppData/Local/Arduino15/packages/Seeeduino", "samd": "${seeeduinoDir}/hardware/samd/1.7.6" }, "configurations": [ { "name": "Win32", "includePath": [ "${ulib}/Adafruit_SSD1306", "${ulib}/Adafruit_GFX_Library", "${samd}/cores/arduino", "${samd}/libraries/SPI", "${samd}/libraries/WIRE", "${samd}/libraries/Adafruit_ZeroDMA", "${samd}/libraries/TimerTCC0", "${samd}/variants/XIAO_m0", "${samd}/**", "${seeeduinoDir}/tools/**" ], "forcedInclude": [ "${samd}/cores/arduino/Arduino.h" ], "intelliSenseMode": "gcc-x64", "compilerPath": "${seeeduinoDir}/tools/arm-none-eabi-gcc/7-2017q4/bin/arm-none-eabi-gcc.exe", "cStandard": "c11", "cppStandard": "c++17", "defines": [ "ARDUINO=10813", "ARDUINO_SAMD_ZERO", "__SAMD21__", "__SAMD21G18A__", "ARM_MATH_CM0_PLUS", "SEEED_XIAO_M0" ] } ], "version": 4 }
includePathで、 ** でサブディレクトリとして 指定してあるのに個別のディレクトリを指定している 部分がある。これは個別指定の方を削除すると エラーになってしまうので仕方なく入れている。
intelliSenseModeとcompilerpathも デフォルトから変えている。 これはデフォルトのままだと pinMode()が未定義というようなエラーが 出るためで、原因はわからない。 intellisenseは謎が多い。
次は
HDDの出力をPWMで制御し、 回転数も検出できるようになった。 次は、フィードバックをかけて 定速制御を行えばいいのだが、 フィードバック・ゲインをどう決めるかが問題。
いつもなら、実験しながら適当に決めてしまうのだが、 ちゃんと特性を計測し、極配置とかで設計してやるのも 面白いかもとか思っている。 どうなることか。