h_nari @ 熊本市のブログ。電子工作、プログラミング、ゲーム、TV、 政治、インターネットなどに日々の思い付きを、 うだうだ~と書いていきたい。
このブログにはコメント欄を設けておりません。 記事への御意見、ご質問はtwitter @h_nari宛に お願い致します。


アーカイブ

メタ情報
RSS
Login

Arduino で XIAOプログラミング

HDDサンダーで 回転数を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個
となっている。 他のマイコン、例えばSTM32だと タイマーやADやDAなどの制御レジスタに これを1にすると入力がTimer-nのX信号が 接続されます、というようなbitがいろいろある。 謎のブロック間ネットワークである。 EVSYSはそれを一般化しているわけで 好意がもてる。 しかし、残念なことに制御レジスタのアドレス空間を ケチっているので使いにくい。

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で制御し、 回転数も検出できるようになった。 次は、フィードバックをかけて 定速制御を行えばいいのだが、 フィードバック・ゲインをどう決めるかが問題。

いつもなら、実験しながら適当に決めてしまうのだが、 ちゃんと特性を計測し、極配置とかで設計してやるのも 面白いかもとか思っている。 どうなることか。