ソースの在り処
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で制御し、
回転数も検出できるようになった。
次は、フィードバックをかけて
定速制御を行えばいいのだが、
フィードバック・ゲインをどう決めるかが問題。
いつもなら、実験しながら適当に決めてしまうのだが、
ちゃんと特性を計測し、極配置とかで設計してやるのも
面白いかもとか思っている。
どうなることか。