1章クロック周波数とレジスタ
Arduino UNO R4のマイコン
MCU(マイコン): RENESAS社製 RA4M1 グループ,型番 R7FA4M1AB3CFM
Arduino UNO R4から見た特徴は...
48MHz Arm Cortex-M4
256KBフラッシュメモリと32KB SRAM
EEPROM のようにデータを保存できる 8KBフラッシュメモリ(※書込回数注意)
14ビット A/Dコンバータ (←Arduino UNO R3では10ビットだった)
12 ビット D/A コンバータ (←Arduino UNO R3にはなかった!)
シリアル通信: UART, SPI, I2C, CAN
です.Arduino UNO R3のマイコン AVRマイコン ATmega328Pのクロック周波数は16MHzでした.
しかし,Arduino UNO R3のA/D変換の分周比(プリスケーラ)はデフォルトでは1/128であったため,analogRead時のA/D変換のクロック周波数は125kHz(=16MHz/128)でした.
その上で,Arduino UNO R3のA/D変換には13クロック,さらにanalogReadの諸作業によって+αクロック加算された時間が1回のanalogReadにかかる時間でした.
そのために,Arduino UNO R3のA/D変換の精度を犠牲にして高速にさせる方法として,「ADPS(ADC Prescaler Select Bits)レジスタ」によって分周比を標準の1/128(=1/2^7)から変更するおまじないとして「ADCSRAレジスタ」の設定を書いたことでしょう.
では,Arduino UNO R4のマイコンRA4M1のクロック周波数48MHzはどういった意味でしょうか?
Arduino UNO R3のように精度を犠牲にしてA/D変換を高速化できるのでしょうか?
図: マイコンRA4M1の「ユーザーズマニュアルーハードウェア編」のp.142 表8.2より引用
クロック周波数は?
ルネサス社製のマイコンRA4M1の「Renesas RA4M1グループ ユーザーズマニュアル ハードウェア編」のp.142の表8.2を見てみましょう.
内部クロックのクロック発生回路の仕様が書かれています.
様々な内部クロックがある中で,よく使いそう(設定できそう)なのは以下の6つくらいです:
システムクロック(ICLK):
用途: CPU/SRAM/Flash/ダイレクトメモリアクセス関連(DTC/DMAC)
最大周波数: 48MHz
分周比: 1, 2, 4, 8, 16, 32, 64周辺モジュールクロックA(PCLKA)
用途: SPI/GPT(General PWM Timer)バスクロック/その他(SCI/SCE5/CRC)
最大周波数: 48MHz
分周比: 1, 2, 4, 8, 16, 32, 64周辺モジュールクロックB(PCLKB)
用途: DAC12/IIC(I2C)/CAN/その他(SSI/DOC/CAC/AGT/PORG/CTSU)
最大周波数: 32MHz
分周比: 1, 2, 4, 8, 16, 32, 64周辺モジュールクロックC(PCLKC)
用途: ADC14変換クロック
最大周波数: 64MHz
分周比: 1, 2, 4, 8, 16, 32, 64周辺モジュールクロックD(PCLKD)
用途: GPTカウントクロック
最大周波数: 64MHz
分周比: 1, 2, 4, 8, 16, 32, 64フラッシュインタフェースクロック(FCLK)
用途: フラッシュインターフェース
周波数: 1MHz~32MHz
分周比: 1, 2, 4, 8, 16, 32, 64
この時点で既にA/D変換のクロックは最大64MHzとなっており,最大周波数48MHzよりも大きいことがわかります.
では,次に,Arduino UNO R4のデフォルトの分周比がどうなっているか,調べていきましょう!
図: マイコンRA4M1の「ユーザーズマニュアルーハードウェア編」のp.146の8.2.1項より引用
分周コントロールレジスタ(SCKDIVCR)
ルネサス社製のマイコンRA4M1の「Renesas RA4M1グループ ユーザーズマニュアル ハードウェア編」のp.146 「8.2.1 システムクロック分周コントロールレジスタ(SCKDIVCR)」を見てみましょう.
まず重要なのは「アドレス」です.
アドレス: SYSTEM.SCKDIVCR 4001 E020h
特に,Arduino IDEから呼び出す際に「SYSTEM.SCKDIVCR」が関わってきます.
また,SCKDIVCRレジスタの各ビット(1要素3ビット分)で各クロックの分周比を設定できます:
システムクロック: ICLK → ICK[2:0] (b26-b24)
周辺モジュールクロックA: PCLKA → PCKA[2:0] (b14-b12)
周辺モジュールクロックB: PCLKB → PCKB[2:0] (b10-b8)
周辺モジュールクロックC: PCLKC → PCKC[2:0] (b6-b4)
周辺モジュールクロックD: PCLKD → PCKD[2:0] (b2-b0)
Flashインターフェイス: FCLK → FCK[2:0] (b30-b28)
また,3ビットの役割は
0 0 0 : 1分周
0 0 1 : 2分周
0 1 0 : 4分周
0 1 1 : 8分周
1 0 0 : 16分周
1 0 1 : 32分周
1 1 0 : 64分周
上記以外の設定は不可
となっています.
さらに,マイコンRA4M1をリセットした場合の値はすべて「1 0 0」に設定されていることから,何もいじっていないマイコンRA4M1のクロックの分周比はすべて1/32であることが読み取れます.
しかし,Arduino UNO R4のデフォルトの分周比がすべて1/32であるとは限りません.
そこで,Arduino UNO R4のデフォルトの分周比を調べるために,Arduino IDEからSCKDIVCRレジスタへアクセスして,分周比を確認してみましょう.
Arduino IDEからSCKDIVCRレジスタの値全体の読み取り
SCKDIVCRレジスタのアドレスは「SYSTEM.SCKDIVCR 4001 E020h」でしたね.
この「SYSTEM.SCKDIVCR」を改造していくことで,Arduino IDEからSCKDIVCRレジスタにアクセスすることができます.
すべてのレジスタで試していないので真偽はわかりませんが,きっと他のレジスタでも同じ方法で基本的にはアクセスできるはずです.
「ドット(.)」を「アロー演算子->」に変える
「SYSTEM->SCKDIVCR」先頭に「R_」を付ける
「R_SYSTEM->SCKDIVCR」
以上です!(簡単ですね~)
実際にArduino IDEでSCKDIVCRレジスタの値を確認するソースコードを書いてみましょう.
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
Serial.print("SYSTEM.SCKDIVCR (HEX) = ");
Serial.println(R_SYSTEM->SCKDIVCR, HEX);
}
void loop() {
// put your main code here, to run repeatedly:
}
結果は,16進数で「10010100」でした.
これを2進数に直すと「0 001 0 000 00000001 0 000 0 001 0 000 0 000」となります.
この値に対してルネサス社製のマイコンRA4M1の「マニュアルーハードウェア」のp.146のSCKDIVCRレジスターの配列の値に書き直すと
FCLK( FCK[2:0] ) : 0 0 1 → 1/2分周比
ICLK ( ICK[2:0] ) : 0 0 0 → 1/1分周比
PCLKA ( PCKA[2:0] ) : 0 0 0 → 1/1分周比
PCLKB ( PCKB[2:0] ) : 0 0 1 → 1/2分周比
PCLKC ( PCKC[2:0] ) : 0 0 0 → 1/1分周比
PCLKD ( PCKD[2:0] ) : 0 0 0 → 1/1分周比
となります.
すなわち,Arduino UNO R4のデフォルトでは最大周波数であるシステムクロック(ICLK)は48MHz,A/D変換(PCLKC)は64MHzで動作していることがわかります.
これで,「Arduino UNO R3のようにArduino UNO R4のA/D変換のクロックの分周比を変えて高速化できないの?という質問」に関する回答は,「分周比の変更によるA/D変換の高速化は不可能」と言えます.
Arduino IDEからSCKDIVCRレジスタの各要素の値の読み取り
レジスタ全体の値の読み取り方法はドット(.)をアロー(->)に変えて,先頭に「R_」を付けることでした.
ですが,毎回,全体を読み取るのは面倒です.
そのために,例えば,SYSTEM.SCKDIVCRのICK[2:0]に直接アクセスできる方法(おそらく,共用体)があります.
「ドット(.)」を「アロー演算子->」に変える
「SYSTEM->SCKDIVCR」先頭に「R_」を付ける (ここまでは全体と変わらない)
「R_SYSTEM->SCKDIVCR」最後尾に「_b」を付ける
「R_SYSTEM->SCKDIVCR_b」最後尾にドットとアクセスしたい要素を指定 (例,ICKの場合)
「R_SYSTEM->SCKDIVCR_b.ICK」
実際に,Arduino IDEでSCKDIVCRレジスタの各要素の値を読み取るソースコードを書いてみましょう:
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
Serial.print("ICK (BIN) = ");
Serial.println(R_SYSTEM->SCKDIVCR_b.ICK, BIN);
Serial.print("PCKA (BIN) = ");
Serial.println(R_SYSTEM->SCKDIVCR_b.PCKA, BIN);
Serial.print("PCKB (BIN) = ");
Serial.println(R_SYSTEM->SCKDIVCR_b.PCKB, BIN);
Serial.print("PCKC (BIN) = ");
Serial.println(R_SYSTEM->SCKDIVCR_b.PCKC, BIN);
Serial.print("PCKD (BIN) = ");
Serial.println(R_SYSTEM->SCKDIVCR_b.PCKD, BIN);
Serial.print("FCK (BIN) = ");
Serial.println(R_SYSTEM->SCKDIVCR_b.FCK, BIN);
}
void loop() {
// put your main code here, to run repeatedly:
}
結果は
ICK (BIN) = 0
PCKA (BIN) = 0
PCKB (BIN) = 1
PCKC (BIN) = 0
PCKD (BIN) = 0
FCK (BIN) = 1
のため,もちろん,前の結果と変わりませんね.
(※2進数表記では001は1と表示され,000は0と表示されます.また,010は10と表示されます.)
もう読み込みはいいから,SCKDIVCRレジスタを変えたいんだ!ってなってきますね.
ちょっとフライングして,
R_SYSTEM->SCKDIVCR_b.PCKC = 0b010;
のようにA/D変換のクロックの分周比を1/4に変えてしまえ,ということ試した方,残念ながら,このままでは,レジスタの値を変更することができません.
プロテクトレジスタによってレジスタの値は書き換え禁止になっています.
そこで,次はプロテクトレジスタの値を書き換えて,レジスタの書き換えを許可する方法を紹介します.
図: マイコンRA4M1の「ユーザーズマニュアルーハードウェア編」のp.262 12.2.1項より引用
プロテクトレジスタ(PRCR)
ルネサス社製のマイコンRA4M1の「Renesas RA4M1グループ ユーザーズマニュアル ハードウェア編」のp.262 「12.2.1 プロテクトレジスタ(PRCR)」を見てみましょう.
まず,重要なのはアドレスです:
SYSTEM.PRCR 4001 E3FEh
次に,SYSTEM.PRCRの要素は以下の4つがあります:
PRC0 : 1ビット
用途: クロック発生回路関連レジスタへの書き込み許可または禁止
使い方: 0: 書き込み禁止, 1: 書き込み許可PRC1 : 1ビット
用途: 低消費電力モードおよびバッテリバックアップ機能関連のレジスタへの書き込み許可または禁止
使い方: 0: 書き込み禁止, 1: 書き込み許可PRC3 : 1ビット
用途: LVD関連レジスタへの書き込み許可または禁止
使い方: 0: 書き込み禁止, 1: 書き込み許可PRKEY : 8ビット
用途: PRCRレジスタへの書き込みを制御
使い方: PRCRレジスタを書き換える場合,上位8ビット(PRKEY)にA5 (※HEX),下位8ビット(PRC0, PRC1, PRC3)に目的の値とし,計16ビット単位で書く
ということで,A/D変換のクロックの分周比を調整するPCLKC (PCKC)を書き換えたい場合,PRC0 を1に書き換える必要があります.
しかし,SYSTEM.PRCRの要素PRC0を0から1に変えるためのプログラム
R_SYSTEM->PRCR_b.PRC0 = 0b1;
を書いても書き換わりません! (->やR_,_bについては,もう大丈夫ですよね?)
その理由がPRKEYにあります.
PRKEYはPRC0, PRC1, PRC3を書き換えるための鍵であり,SYSTEM.PRCRに対して,PRKEYを含めた全16ビットを同時に書き換えなければ,PRC0, PRC1, PRC3を書き換えることができません.
書き換えたい場合,b15からb8に位置するPRKEYは16進数でA5にせよ,と仕様書には書かれています.
その上,b7からb4は2進数で0000であるため,16進数で「0xA50」までは確定しています.
最後のb3からb0の4ビットでPRC0, PRC1, PRC3のフラグを調整します.
PRC0のみ1, PRC1と PRC3は0 → 2進数で0001 → 16進数で1→ 0xA501
PRC0は0, PRC1のみ1, PRC3は0 → 2進数で0010 → 16進数で2 → 0xA502
PRC0とPRC1は0, PRC3のみ1 → 2進数で1000 → 16進数で8 → 0xA508
PRC0,PRC1, PRC3のすべてを0 → 2進数で0000 → 16進数で0 → 0xA500
PRC0,PRC1, PRC3のすべてを1 → 2進数で1011 → 16進数でB → 0xA50B
のように,b3からb0のビットのフラグを調整すれば,プロテクトレジスタのどの要素のフラグを1にするのか指示できます.
もちろん,必要な要素のみフラグを1にして下さい.
また,必ず,使い終わったらプロテクトレジスタのフラグを0にしましょう.
書き換えたいレジスタに対応するプロテクトレジスタのフラグを1にする
書き換えたいレジスタの値を書き換える
使い終わったらプロテクトレジスタのフラグを0にする(0xA500)
実際に,Arduino IDEからプロテクトレジスタを操作するプログラム例を書いてみましょう:
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
Serial.print("Defalt: PRC0 (BIN) = ");
Serial.println(R_SYSTEM->PRCR_b.PRC0, BIN);
R_SYSTEM->PRCR = 0xA501; // PRC0のみ1に変える
Serial.print("Update? PRC0 (BIN) = ");
Serial.println(R_SYSTEM->PRCR_b.PRC0, BIN);
R_SYSTEM->PRCR = 0xA500; // PRC0,PRC1,PRC3を0に変える
Serial.print("Finish: PRC0 (BIN) = ");
Serial.println(R_SYSTEM->PRCR_b.PRC0, BIN);
}
void loop() {
// put your main code here, to run repeatedly:
}
この結果は下記のようになるはずです:
Defalt: PRC0 (BIN) = 0
Update? PRC0 (BIN) = 1
Finish: PRC0 (BIN) = 0
これによって,プロテクトレジスタの要素PRC0のフラグを1にして,クロック周波数の分数比を調整する準備ができました.
いよいよ,次にA/D変換のクロック周波数の分数比を変えてみましょう.
Arduino UNO R4でA/D変換のクロック周波数の分周比を変えてみよう!
ここまでちゃんと読んだ方は,「Arduino IDEにてArduino UNO R4のA/D変換のクロック周波数の分周比を1/2にするプログラムを書け」という問題を出しても,きっと解けるはずです.
もし,時間に余裕があるなら,↑を復習しながら取り組んでみましょう!
正解:
R_SYSTEM->PRCR = 0xA501;
R_SYSTEM->SCKDIVCR_b.PCKC = 0b001;
R_SYSTEM->PRCR = 0xA500;
復習も兼ねて1行ずつ解説していきましょう.
まず,
R_SYSTEM->PRCR = 0xA501;
は,プロテクトレジスタ(PRCR)のPRC0を1にする,という意味でしたね.
特に「PRC0を1にする」というのは,「クロック発生回路関連レジスタへの書き込みを許可する」という意味でした.
これで,クロック発生回路関連レジスタへ書き込みができるようになりました.
続いて
R_SYSTEM->SCKDIVCR_b.PCKC = 0b001;
です.
これは,SCKDIVCRレジスタの要素PCKCに0 0 1をセットする,という意味でしたね.
「PCKCに0 0 1をセットする」というのは,「ADC14変換クロックである周辺モジュールクロックC(PCLKC)を2分周に設定する」しています.
例えば,応用として,PCLKCを0 1 0 にセットしたら,A/D変換のクロックの分数比を4分周に設定できましたね.
最後に,
R_SYSTEM->PRCR = 0xA500;
は,プロテクトレジスタ(PRCR)のPRC0,PRC1, PRC3をすべてを0にする,という意味でした.
すなわち,「レジスタの書き込みを禁止する」という意味になります.
実際にArduino IDEで書き換わっているか,確かめてみましょう:
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
Serial.print("Default PCKC: ");
Serial.println(R_SYSTEM->SCKDIVCR_b.PCKC, BIN);
R_SYSTEM->PRCR = 0xA501;
R_SYSTEM->SCKDIVCR_b.PCKC = 0b001;
R_SYSTEM->PRCR = 0xA500;
Serial.print("Update PCKC: ");
Serial.println(R_SYSTEM->SCKDIVCR_b.PCKC, BIN);
}
void loop() {
// put your main code here, to run repeatedly:
}
これで,A/D変換のクロックの分周比が1/2に変わったはずです.
しかし,これでは,本当に遅くなったのか実感がわきませんね...
そこで,次は,実際に分周比を変えることでanalogReadの速度が遅くなることを確認してみましょう!
A/D変換のクロックの分周比によるanalogReadの時間変化を見てみよう!
少々長いですが,まずはテスト用のプログラムを紹介します.
プログラムの後に重要な個所を説明していきます:
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}
void loop() {
// put your main code here, to run repeatedly:
R_SYSTEM->PRCR = 0xA501;
R_SYSTEM->SCKDIVCR_b.PCKC = 0b000;
R_SYSTEM->PRCR = 0xA500;
Serial.println("PCKC = 0b000 (prescaler: 1, Clock: 64MHz)");
speedtest(64);
R_SYSTEM->PRCR = 0xA501;
R_SYSTEM->SCKDIVCR_b.PCKC = 0b001;
R_SYSTEM->PRCR = 0xA500;
Serial.println("PCKC = 0b001 (prescaler: 2, Clock: 32MHz)");
speedtest(32);
R_SYSTEM->PRCR = 0xA501;
R_SYSTEM->SCKDIVCR_b.PCKC = 0b010;
R_SYSTEM->PRCR = 0xA500;
Serial.println("PCKC = 0b010 (prescaler: 4, Clock: 16MHz)");
speedtest(16);
R_SYSTEM->PRCR = 0xA501;
R_SYSTEM->SCKDIVCR_b.PCKC = 0b011;
R_SYSTEM->PRCR = 0xA500;
Serial.println("PCKC = 0b011 (prescaler: 8, Clock: 8MHz)");
speedtest(8);
R_SYSTEM->PRCR = 0xA501;
R_SYSTEM->SCKDIVCR_b.PCKC = 0b100;
R_SYSTEM->PRCR = 0xA500;
Serial.println("PCKC = 0b100 (prescaler: 16, Clock: 4MHz)");
speedtest(4);
R_SYSTEM->PRCR = 0xA501;
R_SYSTEM->SCKDIVCR_b.PCKC = 0b101;
R_SYSTEM->PRCR = 0xA500;
Serial.println("PCKC = 0b101 (prescaler: 32, Clock: 2MHz)");
speedtest(2);
R_SYSTEM->PRCR = 0xA501;
R_SYSTEM->SCKDIVCR_b.PCKC = 0b110;
R_SYSTEM->PRCR = 0xA500;
Serial.println("PCKC = 0b110 (prescaler: 64, Clock: 1MHz)");
speedtest(1);
delay(60000);
}
void speedtest(int clock_value){
unsigned long StartTime = millis();
int max_iter = 1024;
for(long i=0; i < max_iter; i++){
int res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
res = analogRead(A0);
}
unsigned long StopTime = millis();
double analogread_time = double(StopTime - StartTime)*1000/(max_iter*20);
Serial.print("The time per analogRead is ");
Serial.print(analogread_time);
Serial.println(" [μs]");
Serial.print("Equation: ");
Serial.print("1/48*x + 1/");
Serial.print(clock_value);
Serial.print("*y = ");
Serial.println(analogread_time);
Serial.println("");
}
まず,このプログラムには以下の3つの関数があります:
void setup()
void loop()
void speedtest(int prescaler){
関数setup()は特に何も書かれていないため,何も実行されません.
関数loop()では,
A/D変換のクロックの分周比を変更する(分周: 1, 2, 4, 8, 16, 32, 64)
Serial.printlnで今の分周を表示する
関数speedtestを1.で変更された分周比で実行する
を繰り返しています.
すなわち,関数loop内で説明していないのは関数speedtestだけ,他は読み解けるはずです.
続いて関数speedtestの中身について紹介します.
関数speedtestでは,20回のanalogReadをfor文で1024回繰り返し,そのときにかかった時間を計測しています.
すなわち,20480回のanalogReadにかかった時間を計測しています.
その上で,analogReadを1回実行するにあたりかかる時間を
double(StopTime - StartTime)*1000/(max_iter*20);
で計算しています.
計測された時間に対し,「*1000」で[ms]を[μs]に変換し,20480回(= max_iter*20)で割っています.
また,for文の影響をなくすために,1回のanalogReadをfor文で20480回繰り返すプログラムにしていません.
すなわち,ループアンローリング(ループの展開)によってをfor文の終了条件の判定回数などを減らしています.
実際に,ループの展開数を1→10→20と変えていくと,若干ながら計測時間が速くなりました.
それに対し,ループの展開を20→40に変えても,計測時間に影響がほぼ見られませんでした.
そのために,analogReadの計測時におけるfor文の影響はループの展開を20くらいにすれば見えなくなると推察しました.
なお,結果を代入するres=は計測時間に影響はありませんでしたが,普段,analogReadを使うことを想定して書いております.
また,関数speedtest内の
Serial.print("Equation: ");
以降に何か色々と出力させていますが,次の「1回のanalogReadにおけるクロック数は?」で説明しますので,ここでは無視をしてください.
その結果,1回のanalogReadにおける時間は次のような結果が得られました:
1分周 (64MHz): 21.39 [μs] ※ Arduino UNO R4のデフォルト
2分周 (32MHz): 23.78 [μs]
4分周 (16MHz): 28.37 [μs]
8分周 (8MHz): 37.35 [μs]
16分周 (4MHz): 55.42 [μs]
32分周 (2MHz): 91.41 [μs]
64分周 (1MHz): 164.06 [μs]
この結果をみると,A/D変換の分周を大きくすることで,関数analogReadの速度が遅くなることが見てとれます.
しかし,64MHzのときは21.39 [μs]だったのに対し,32MHzに設定した際は23.78 [μs]と1.116倍程度遅くなるだけで,純粋に2倍遅くなるわけではないことに気が付きます.
64MHzは1秒間に64,000,000クロックあることを意味するため,1回あたりのクロックの時間は0.015625 [μs]になります.
同様に,32MHzは1秒間に32,000,000クロックあることを意味するため,1回あたりのクロックの時間は0.03125[μs]になります.
analogReadはアナログ信号をディジタル信号に変換するA/D変換を行うはずなので,A/D変換の分周を2倍大きくしたら,単純に2倍近く遅くなるはずだ,と思いたいですが,そうはなっていません.
感の鋭い人は,もうお気づきでしょう.
「クロック周波数は?」で紹介したように,マイコンRA4M1には様々なクロックがありました.
時間計測しているところは,あくまで,analogRead(と影響を少なくしたfor文)のみのため,その他の周辺クロックであるPCLKA, PCLKB, PCLKD, FCLKは影響がないと仮定します.
今回,分周比を変更したのはADC14変換クロックであるPCLKCのみで,システムクロックICLKはデフォルトの48MHzの設定のままです.
そのとき,analogReadの主な時間はA/D変換ではなく,システムクロックICLKが扱う計算ではないか?と推察できます.
そこで,本章の最後として,1回のanalogReadにおける「A/D変換用クロックPCLKCのクロック数」と「システムクロックICLKのクロック数」を推察してみましょう!
図. analogReadのクロック数の推察
1回のanalogReadにおけるクロック数を推察しよう!
ここでも前節と同様にArduino UNO R4のArduino APIのanalogReadでは,シリアル通信やDACなどは行われていない,すなわち,analogReadではPCLKA, PCLKB, PCLKD, FCLKは影響がないと仮定して話を進めます.
本節では前節の測定結果を用いて,2変数(x, y)の連立方程式に帰着し,クロック数を推察します.(特に,前節で一旦忘れてもらったEquationに関係します!)
まず,1回のanalogReadにかかる時間は
システムクロック(ICLK)による演算時間 + A/D変換のクロック(PCLKC)による演算時間
と書けます.
次に未知変数として
x : システムクロック(ICLK)を用いたクロック数
y : A/D変換のクロック(PCLKC)を用いたクロック数
とします.
そのとき,
システムクロック(ICLK)を用いた演算時間 = 1クロックの時間*x
となります.
さらにシステムクロック(ICLK)は48MHzで固定しているため1クロックあたりの時間[μs]は1/48( = 1/48000000*1000*1000) となります.
よって,
システムクロック(ICLK)を用いた演算時間[μs] = 1/48[μs] * x[回]
となります.
同様に,「A/D変換のクロック(PCLKC)を用いた演算時間」も考えていきます.
但し,A/D変換のクロック(PCLKC)は分周比を変化させているため,
f[MHz] : 分周したA/D変換のクロック周波数
とします.
すなわち,PCLKCを1分周にしたときはf=64 [MHz]を意味し,2分周にしたときはf=32[MHz]を意味します.
このとき,A/D変換のクロック(PCLKC)を利用した場合の1クロックあたりの時間[μs]は1/f [μs] (= 1/(f*1000*1000)*1000*1000)となります.
よって,
A/D変換のクロック(PCLKC)を用いた演算時間[μs] = 1/f[μs] * y[回]
となります.
以上から,PCLKCをn分周にしたとき,方程式
1/48[μs] * x[回] + 1/f[μs] * y[回] = 1回のanalogReadにかかる時間[μs]
が得られます.
例えば,PCLKCを1分周(f=64MHz)にしたとき,前節の測定結果から1回のanalogReadにかかる時間は21.39 [μs] であったことを利用すると
1/48[μs] * x[回] + 1/64[μs] * y[回] = 21.39 [μs]
という方程式になります.
以上を他の分周比の場合もまとめると7つの方程式
1分周(64MHz): 1/48[μs] * x[回] + 1/64[μs] * y[回] = 21.39 [μs]
2分周(32MHz): 1/48[μs] * x[回] + 1/32[μs] * y[回] = 23.78 [μs]
4分周(16MHz): 1/48[μs] * x[回] + 1/16[μs] * y[回] = 28.37 [μs]
8分周(8MHz): 1/48[μs] * x[回] + 1/8[μs] * y[回] = 37.35 [μs]
16分周(4MHz): 1/48[μs] * x[回] + 1/4[μs] * y[回] = 55.42 [μs]
32分周(2MHz): 1/48[μs] * x[回] + 1/2[μs] * y[回] = 91.41 [μs]
64分周(1MHz): 1/48[μs] * x[回] + 1/1[μs] * y[回] = 164.06 [μs]
を得られます.
これが,前節の結果で表示していたEquationの意味です.
未知変数が2変数であるため,上記の内,任意の2つ方程式を選んで連立方程式として解けば,「システムクロック(ICLK)を用いたクロック数x」と「A/D変換のクロック(PCLKC)を用いたクロック数y」が推察できます.
方程式の数が7つあり,そこから2つ組み合わせを選ぶため,全部で21通りあります.
21通りに対し,解は得るプログラムをMatlabで書くと下記のようになります:
A_all = [
1/48, 1/64;
1/48, 1/32;
1/48, 1/16;
1/48, 1/8;
1/48, 1/4;
1/48, 1/2;
1/48, 1/1;
];
b_all = [
21.39;
23.78;
28.37;
37.35;
55.42;
91.41;
164.06;
];
A = zeros(2,2);
b = zeros(2,1);
res = zeros(2,15);
k = 1;
for i = 1:6
for j = i+1:7
A(1,:) = A_all(i,:);
A(2,:) = A_all(j,:);
b(1) = b_all(i);
b(2) = b_all(j);
x = A\b;
res(:,k) = x;
k = k + 1;
end
end
上記に対し,箱ひげ図は
boxchart(res(1,:)')
や
boxchart(res(2,:)')
で書くことができます.
実際に全てのパターンで解いた解は下記のようになります:
1分周と2分周の解 x = 912.0000 152.9600
1分周と4分周の解 x = 915.0400 148.9067
1分周と8分周の解 x = 917.2800 145.9200
1分周と16分周の解 x = 917.8240 145.1947
1分周と32分周の解 x = 918.3019 144.5574
1分周と64分周の解 x = 918.0190 144.9346
2分周と4分周の解 x = 921.1200 146.8800
2分周と8分周の解 x = 924.3200 144.7467
2分周と16分周の解 x = 924.4800 144.6400
2分周と32分周の解 x = 925.0240 144.2773
2分周と64分周の解 x = 924.2323 144.8052
4分周と8分周の解 x = 930.7200 143.6800
4分周と16分周の解 x = 928.9600 144.2667
4分周と32分周の解 x = 929.4857 144.0914
4分周と64分周の解 x = 927.5520 144.7360
8分周と16分周の解 x = 925.4400 144.5600
8分周と32分周の解 x = 927.8400 144.1600
8分周と64分周の解 x = 923.9314 144.8114
16分周と32分周の解 x = 932.6400 143.9600
16分周と64分周の解 x = 921.9200 144.8533
32分周と64分周の解 x = 900.4800 145.3000
上記の結果や「図. analogReadのクロック数の推察」から
システムクロック(ICLK)を用いたクロック数: 917~929 クロック
A/D変換のクロック(PCLKC)を用いたクロック数: 144~146 クロック
くらいなのではないか?と推察することができます.(あくまで,推察であり,正しさは保証されていませんのでご注意ください.)
この結果から,デフォルト (ICLK: 48MHz, PCLKC: 64MHz) のanalogReadの1回あたりの時間の割り振りは,
ICLK: 19.1[μs] ~ 19.35[μs]
PCLKC: 2.25[μs] ~ 2.28[μs]
合計: 21.35[μs] ~ 21.63[μs]
くらいと計算できます.
このことから,Arduino APIのanalogReadはA/D変換にかかる時間よりも,ほかの処理にかかる時間が主要なのではないか?と考えられます.
そこで,次はanalogReadのソースコードを解析し,analogReadでシステムクロックを使う時間を減らし,A/D変換に特化させる方法を考えていきましょう!