google-site-verification: google3bd66dd162ef54c7.html
fc2ブログ

Arduino UNO R4 のレジスタ操作解説とピン設定ダンププログラム

◆まえがき
Arduino UNO R4 いじりの話の3回目。前回は アナログポートの挙動を詳しく調査 しましたが、今回はCPUのレジスタ操作方法についてまとめます。

CPUのIO機能の細かい設定を行うためには、内部のレジスタを操作する必要があります。ソフトからはレジスタの名前でアクセスする訳ですが、その指定方法が UNO R3 とは少し違っていたので、そのあたりを整理しておきたいと思います。なお、C言語をちゃんと習った方には当たり前の話が多いのかも知れません。

◆参考URL
このあたりの話、私は素人同然なのでネットの情報を頼りに調べて行きました。参考にさせていただいたのは主に下記サイトです。ありがとうございました。
Arduino UNO R4でポートレジスタ直接制御 (小倉 キャッスル 一馬さん)などの一連の記事
RAマイコンを試食(7) - ピン設定 (なんとなく活動記録。さん)

◆レジスタの指定方法
上記にURLを書いた小倉キャッスルさんが丁寧に解説されていますが、復習を兼ねて私が知らなかったことを中心に振り返ってみます。以下の例は、小倉キャッスルさんの記事に最初に出て来る、D13ピンを出力にアサインするコードです。

例1 R_PORT1->PCNTR1 |= 1 << 11;
・最初の R_ から見たことが無い表現です。これは、レジスタはグループ分けされていて最上位のレジスタグループ名に  R_ を付けるというルールがあるようです(違うかも)。つまりこの例では、PORT1というレジスタグループ名を示しています。このCPUには大量のレジスタがあるので、グループ分けしておかないといろいろ不便なんだと思います。

・R_PORT1の次の -> はアロー演算子で構造体の構成要素を指定するために使われています。
ちなみに、R言語では <- は代入(向きが逆)。pythonだと -> はアノテーションなのでこれを最初に見た時はピンと来なかったです。

Arduino IDE の 2.0以降なら、アロー演算子の > を入力した直後に下図のように
PORT1の選択肢
そのレジスタで選択可能な項目がプルダウンで表示されるので、そこから選べば間違いを防ぐことが出来ます。これ以降の階層でも、そこで選択可能な項目名が列挙されます。

・PCNTR1は32ビットのポートコントロールレジスタになります。(PCNTR1の上位16ビットにはPODR、下位16ビットにはPDRという名前が付いているので、そちらの名前で呼び出すことも可能です。)
この例ではPCNTR1の11ビット目に1を書いてP111ポート(D13ピン)を出力にアサインしています。

例2 R_PORT1->PDR |= 1 << 11;
前項はPCNTRを指定していたのに対し、この例では下位の16ビットのPDRを呼び出しています。指定方法は前項と同じでビット操作で行っています。

例3 R_PORT1->PDR_b.PDR11 = 1;
ここで新しいルールの登場です。前項のPDRの代わりにPDR_bを指定すると、そのレジスタの中の要素名を指定出来るようになります。_bはビットあるいはバイナリー要素と言う意味でしょうか。この例ではPDRレジスタの中の11ビット目であるPDR11が選択されています。

例3+ R_PORT1->PCNTR1_b.PDR11=1;
引用元には書かれていませんが、PDR11はPCNTR1の要素に含まれているので、上記のようにPCNTR1_b.PDR11;と書く手もあると思います。

例4 R_PFS->PORT[1].PIN[11].PmnPFS |= 1 << 3;
PmnPFSレジスタでポートの機能を詳細に指定する方法です。この方法ではより細かいポートの機能の指定が可能になります。内容としては、
R_PFSでレジスタグループを指定。->のアロー演算子の後にポートとピン番号を指定。最後に辿り着いたPmnPFSレジスタの下から3つ目のビットを立てることで出力ピンに指定しています。

例5 R_PFS->PORT[1].PIN[11].PmnPFS_b.PDR = 1;
この例ではPmnPFS_bと指定することでビット要素での指定を可能とし、その要素名のPDRに1を書くことで出力を指定しています。

◆データーシートとの関係
例4と5で指定しているPFS->PmnPFSレジスタのデーターシートは下記です
PmnPfsSetting

アドレス名の先頭がPFSなので R_PFSと書いてレジスタグループを指定します。ポートとピン名は配列と構造を組み合わせて指定されているようです。R_PFS->PORT[1].PIN[11].PmnPFS_b.PDR = 1; と書けばこのレジスタのPDRに1を書くことが出来ます。

PDRのビットはオレンジ色の矢印で示すようにPmnPFS_BYに含まれているので、
例5は、R_PFS->PORT[1].PIN[11].PmnPFS_BY_b.PDR = 1; と書いても大丈夫だと思います。

◆ポートの設定状態を読み取り
指定したレジスタの記述を代入するだけで読めるので、例えば
x = R_PFS->PORT[1].PIN[11].PmnPFS;
と書けばOKです。上記の例では32ビットの値が返ってくるので xは uint32_t で定義。

レジスタによってはプロテクトが掛かっているために、正しく書けていない場合があります。本当に書けている、そのレジスタを読み出して確認した方が良いです。

◆プログラム例
ポート操作の話の締め括りとして、Arduino UNO R4 MINIMA のボードの全IOピンの設定状態をダンプするプログラムを作ってみました。上に書いたように思った通りに書けているかを確認することが重要だと思います。IOピンのピン毎に関数を分けてあるので必要部分をコピペして使うと便利だと思います。また、他のレジスタの確認プログラムも、これを雛形にして作れば楽に出来ると思います。
注:これはR4 MINIMA用です。R4 WiFi はピンアサインが異なる部分があるのでこのままでは使えません。
// Arduino UNO R4 MINIMAのIOピンの初期状態をダンプ
// 20240213_UnoR4MinimaPinDeffDump
// ラジオペンチ http://radiopench.blog96.fc2.com/

char cbuff[10];  // 文字列操作バッファ

void setup() {
  Serial.begin(115200);
  delay(1000);  // while(!Serial){} は使えなかった
}

void loop() {
  Serial.println();
  Serial.println("Arduino UNO R4 MINIMA PFS dump");
  viewD0();
  viewD1();
  viewD2();
  viewD3();
  viewD4();
  viewD5();
  viewD6();
  viewD7();
  viewD8();
  viewD9();
  viewD10();
  viewD11();
  viewD12();
  viewD13();
  viewA0();
  viewA1();
  viewA2();
  viewA3();
  viewA4();
  viewA5();
  delay(200);
}

void viewD0() {                                                          // D0
  sprintf(cbuff, "D0  (P301) = 0x%08lx", R_PFS->PORT[3].PIN[1].PmnPFS);  // 対象ポートのPFSを16進8桁の文字列に変換
  Serial.println(cbuff);
}
void viewD1() {  // D1
  sprintf(cbuff, "D1  (P302) = 0x%08lx", R_PFS->PORT[3].PIN[2].PmnPFS);
  Serial.println(cbuff);
}
void viewD2() {  // D2
  sprintf(cbuff, "D2  (P105) = 0x%08lx", R_PFS->PORT[1].PIN[5].PmnPFS);
  Serial.println(cbuff);
}
void viewD3() {  // D3
  sprintf(cbuff, "D3  (P104) = 0x%08lx", R_PFS->PORT[1].PIN[4].PmnPFS);
  Serial.println(cbuff);
}
void viewD4() {  // D4
  sprintf(cbuff, "D4  (P103) = 0x%08lx", R_PFS->PORT[1].PIN[3].PmnPFS);
  Serial.println(cbuff);
}
void viewD5() {  // D5
  sprintf(cbuff, "D5  (P102) = 0x%08lx", R_PFS->PORT[1].PIN[2].PmnPFS);
  Serial.println(cbuff);
}
void viewD6() {  // D6
  sprintf(cbuff, "D6  (P106) = 0x%08lx", R_PFS->PORT[1].PIN[6].PmnPFS);
  Serial.println(cbuff);
}
void viewD7() {  // D7
  sprintf(cbuff, "D7  (P107) = 0x%08lx", R_PFS->PORT[1].PIN[7].PmnPFS);
  Serial.println(cbuff);
}
void viewD8() {  // D8
  sprintf(cbuff, "D8  (P304) = 0x%08lx", R_PFS->PORT[3].PIN[4].PmnPFS);
  Serial.println(cbuff);
}
void viewD9() {  // D9
  sprintf(cbuff, "D9  (P303) = 0x%08lx", R_PFS->PORT[3].PIN[3].PmnPFS);
  Serial.println(cbuff);
}
void viewD10() {  // D10
  sprintf(cbuff, "D10 (P112) = 0x%08lx", R_PFS->PORT[1].PIN[12].PmnPFS);
  Serial.println(cbuff);
}
void viewD11() {  // D11
  sprintf(cbuff, "D11 (P109) = 0x%08lx", R_PFS->PORT[1].PIN[9].PmnPFS);
  Serial.println(cbuff);
}
void viewD12() {  // D12
  sprintf(cbuff, "D12 (P110) = 0x%08lx", R_PFS->PORT[1].PIN[10].PmnPFS);
  Serial.println(cbuff);
}
void viewD13() {  // D13
  sprintf(cbuff, "D13 (P111) = 0x%08lx", R_PFS->PORT[1].PIN[11].PmnPFS);
  Serial.println(cbuff);
}
void viewA0() {  // A0
  sprintf(cbuff, "A0  (P014) = 0x%08lx", R_PFS->PORT[0].PIN[14].PmnPFS);
  Serial.println(cbuff);
}
void viewA1() {  // A1
  sprintf(cbuff, "A1  (P000) = 0x%08lx", R_PFS->PORT[0].PIN[0].PmnPFS);
  Serial.println(cbuff);
}
void viewA2() {  // A2
  sprintf(cbuff, "A2  (P001) = 0x%08lx", R_PFS->PORT[0].PIN[1].PmnPFS);
  Serial.println(cbuff);
}
void viewA3() {  // A3
  sprintf(cbuff, "A3  (P002) = 0x%08lx", R_PFS->PORT[0].PIN[2].PmnPFS);
  Serial.println(cbuff);
}
void viewA4() {  // A4
  sprintf(cbuff, "A4  (P101) = 0x%08lx", R_PFS->PORT[1].PIN[1].PmnPFS);
  Serial.println(cbuff);
}
void viewA5() {  // A5
  sprintf(cbuff, "A5  (P100) = 0x%08lx", R_PFS->PORT[1].PIN[0].PmnPFS);
  Serial.println(cbuff);
}

正常に動くと、シリアルにポートのPFSの32ビットを16進形式でダンプします。
Arduino UNO R4 MINIMA PFS dump
D0 (P301) = 0x00000000
D1 (P302) = 0x00000000
D2 (P105) = 0x00000000
D3 (P104) = 0x00000000
D4 (P103) = 0x00000000
D5 (P102) = 0x00000000
D6 (P106) = 0x00000000
D7 (P107) = 0x00000000
D8 (P304) = 0x00000000
D9 (P303) = 0x00000000
D10 (P112) = 0x00000000
D11 (P109) = 0x00010000
D12 (P110) = 0x00010012
D13 (P111) = 0x00000004
A0 (P014) = 0x00000000
A1 (P000) = 0x00000000
A2 (P001) = 0x00000000
A3 (P002) = 0x00000000
A4 (P101) = 0x00000002
A5 (P100) = 0x00000002

このダンプは繰り返し表示します。
リセット直後はD13以外は入力ピンになっているので、IOピンに電圧を加えると状態が変化する様子を見ることが出来ます。

これ見ると、D11とD12には特別な機能が割り付けられているようです。
また、D12は入力のプルアップに指定されています。ということは、

・D12でLEDが光った
D12でLEDが光った
リセット後の UNO R4 の D12ピンはプルアップ(約20kΩ)されているのでLEDを繋ぐと光る。

◆まとめ
これで UNO R4 のレジスタの操作方法をマスターしました(したと思う)。設定の確認プログラムも出来たので、次は気になっているクロック周波数の確認と調整にチャレンジする予定です。

記事中に書き忘れましたが、操作するビットの要素はその名前を書いておいた方がコードが読み易くなると思います。つまり、例1や例2のように11ビット目と書くのではなく、PDR11と明記しておいた方が判り易いコードになると思います。

◆話の整理
CPUレジスタのアクセス方法を UNO R3 などと比較すると、
1. R_周辺機能名->レジスタ名.[レジスタ名] の階層構造で目的のレジスタを指定する。
2. レジスタ内のビット(集合)を指定したい場合は、レジスタ名_b.ビット(集合)名の形式で指定する。
3. 入力可能な項目名はプルダウンメニューに出て来るので、その中から選択するとミスを防げる。(IDE2.0以上の場合)

関連記事

コメントの投稿

管理者にだけ表示を許可する

No title

実例を上げてUNO R4のレジスタ操作方法を解説して頂き、大変参考になります。
(理解が間違っていたらすみません。)
Aanalog Readのスピードは、実測値ですがR3が110uS程度、R4は8bitでも14bitでも25uS程度でしたが、R3はADCSRAレジスタを変更すると、20uS程度までは向上します。
解説して頂いたレジスタ直読み込みなら、R4でもAanalog Readのスピードが、かなり改善されそうで楽しみです。(今度試してみます。)

パオさん、コメントありがとうございます

そうですね。レジスタ操作でADCが高速化出来ると使い道が増えますよね。
あと、前の記事で指摘した問題が出ないように設定出来れば最高です。
カレンダー
03 | 2024/04 | 05
- 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 - - - -
プロフィール

ラジオペンチ

Author:ラジオペンチ
電子工作を中心としたブログです。たまに近所(東京都稲城市)の話題など。60過ぎて視力や器用さの衰えを感じつつ日々挑戦!
コメントを入れる時にメールアドレスの記入は不要です。なお、非公開コメントは受け付けていません。
記事の内容のご利用は読者の自己責任でお願いします。

記事が気に入ったらクリックを!
最新記事
カテゴリ
最新コメント
リンク
FC2カウンター
検索フォーム
月別アーカイブ
RSSリンクの表示
QRコード
QRコード