トホホな疑問(40) ATSAMD51、Port Event制御1個しか動かないんですけど

Joseph Halfmoon

ATSAMD51マイコンのEVSYS、CPUのお手を煩わせずいろいろできて良いものです。その実験をしていて問題が一つ。イベントでPORTを操作してみたところ、1ポートグループあたり4端子まで制御出来る筈が1端子しか反応しませぬ。なにがいけない? Microchip社のエラッタも念のため見たけど、書いてないです。

このところ別シリーズにて、Microchip社のATSAMD51マイコンを「ハードに近いところ」で動かしてみているのであります。実は先週はEVSYS、Event Systemというものを実験してみるつもりでした。しかし、うまく行かなかったことがあり、別なホンワカした題材に逃げてました。

EVSYSは周辺回路で発生したイベントをCPUの手を煩わせずに他の周辺回路に伝達するしくみです。昔風のマイコンにもCPUを介さずデータ転送可能なDMAコントローラがありますが、SAMD51にはDMA以外に各種周辺回路の動作の引き金を引けるEVSYSもあり、とてもモダンな感じがしておりました(年寄りには。)

さてEVSYS自身は「誰かから誰かさんへ」伝達するための仕組みです。行き先の「誰かさん」にあたる周辺回路は USER と呼ばれているみたいです。合計で32チャンネルのUSERさんを取り扱えます。実験ではお手軽にイベントがUSER回路に伝わったことを外部に出力したかったのです。外部出力ということで安直にPORTをUSERに選んで先週はツボにハマりました。私がデータシートを読むと以下のように読めます。

  • ポートは最大32端子毎のポートグループとして構成されている
  • ポートグループ0は、Port AのPAxx端子(xxには0から31)が所属している。グループ1はBなど以下同文。
  • CPUからの読み書き、ペリフェラル機能の入出力とは別に、EVSYSから伝わってきたイベント信号でポートの出力を制御できる
  • イベントにより、ポート出力をセット、クリア、トグルなどさせることができる。
  • 各ポートグループのイベント制御は各ポートグループのEVCTRLレジスタで指定する。
  • EVCTRLレジスタは32ビット長であり、8ビット毎に分割され、4個のイベント設定を行える。
  • 1個のイベント設定は端子1本を制御する。

よって1ポートグループ毎に4本の端子を操作できるという理解です。EVCTRLの複数のイベント制御フィールドに同じ端子番号をセットしてしまうことは排除できないようなので、その場合のルールなども書かれていました。

手元でWio Terminal搭載のATSAMD51P19Aマイコンを末尾の実験コードで動かして観察したところ、問題は以下のようです。

  1. EVCTRLレジスタのビット15~ビット8の1エントリのみ有効
  2. ビット31からビット16およびビット7からビット0の3エントリ反応せず

でした。いろいろやってみたんですけどね。何か設定に悪いところがあるじゃないか、と思ってかなりやったですが、煮詰まった頭ではこれ以上進まないので「トホホ」に入れました。まあね、Microchip社のSAM_D5x_E5x_Family_Errataというのも一応見ましたがが掲載ありませぬ。

なお、Microchip社のエラッタは以下の製品ページから検索する方が良いかもです。

ATSAMD51P19A | Microchip Technology

実験の設定

ポートからのEVENT出力を外部で確認するために、ポートグループ0と1それぞれに2端子づつ、合計4本端子に外部にLEDをとりつけました(先頭のアイキャッチ画像。)たまたま外部出力しやすい端子を選んだだけであります。

とりつけた4端子は以下のとおりです。ソフトを書くときに「出来合いの」関数に与える番号は「Arduino」ですが、Wio Terminalの拡張端子は、Raspberry Piの拡張端子と互換配列になっているので、信号を取り出すときはRaspberry Pi式です。今回のようにSAMD51のハードを直接プログラムするときはSAMD51の端子名(とポートグループ)を念頭に書かないとなりませぬ。最近慣れましたがメンドイ。

SAMD51 Raspberry互換拡張端子名 Wio Terminal信号名 Arduino端子番号
PB08 GPIO27 A0/D0 0
PB09 GPIO22 A1/D1 1
PA04 GPIO13 A6/D6 6
PA06 GPIO26 A8/D8 8

さて、EVSYSと、ポートグループは以下のように接続設定しています。

  • EVSYSのCH0をポートグループ0 (Port A)に割りあて
  • EVSYSのCH31をポートグループ1 (Port B)に割りあて

そしてそれぞれのポートグループのEVCTRLレジスタに以下の設定を書き込みました。

  • ポートグループ0 PA04とPA06 いずれもイベント許可、動作トグル
  • ポートグループ1 PA08とPA09 いずれもイベント許可、動作トグル

そして実験のため毎秒「ソフトウエアで」EVSYSのSWEVTレジスタにあるCH31(MSB)とCH0(LSB)に対応するビットに1を書き込むことでイベントを発生させています。SWEVTレジスタはソフトウエアでイベントを起こさせるためのもので、今回みたいな実験には適します。

期待としては、4端子に取り付けたLEDが毎秒トグル(点滅)する、です。

実験結果

ポートグループ0、1に関わりなく同じ結果を得ました。以下はグループ0の例ですが、下から2番目のバイトの0xE6(端子6番をトグル)は期待通りの動作をしますが、最下位バイトの0xE4(端子4番をトグル)は動作しません。E6とE4の場所を交換すると今度はE4が点滅し、E6はダメになります。上位2か所にも単独で設定を書き込みましたが動作せずです。結局1端子分でも2端子分でも下から2番目のバイトに書き込んだ設定しか動作しなかったです。

PORT->Group[0].EVCTRL.reg = 0x0000E6E4;

とりあえず1端子は動作するので、やろうと思っていた実験はできますが、なんだかな~ どうしたのかな~ トホホ。。。

トホホな疑問(39) ラズパイPico、C/C++SDKからのPWM出力でトホホが2つ へ戻る

トホホな疑問(41) ラズパイmicroSDのバックアップ、リストアの落とし穴? へ進む

実験に使用したソース全文

VS Code上でPlatformIOを使わせていただき、ボードはWio Termina、プラットフォームはArduinoを指定したプロジェクトでビルドしています。

// SAMD51 EVSYS test
#include <Arduino.h>

// GPIO27 (WIO:A0/D0, SAMD51:PB08, Arduino:0) 
// GPIO22 (WIO:A1/D1, SAMD51:PB09, Arduino:1) 
// GPIO13 (WIO:A6/D6, SAMD51:PA04, Arduino:6) 
// GPIO26 (WIO:A8/D8, SAMD51:PA06, Arduino:8) 
#define GPIO27  (0)
#define GPIO22  (1)
#define GPIO13  (6)
#define GPIO26  (8)

uint32_t counter;

void setupEVSYS() {
  MCLK->APBBMASK.bit.EVSYS_ = 1; //Enable EVSYS_APB
  EVSYS->USER[EVSYS_ID_USER_PORT_EV_0].bit.CHANNEL = 0x1;   //User Multiplexer 1(PortA) <= EvChannel 0
  EVSYS->USER[EVSYS_ID_USER_PORT_EV_1].bit.CHANNEL = 0x20; //User Multiplexer 1(PortB) <= EvChannel 31
}

void setupPortEvent() {
  PORT->Group[0].EVCTRL.reg = 0x0000E6E4; //PA04 Toggle, PA06 Toggle
  PORT->Group[1].EVCTRL.reg = 0x0000E8E9; //PB08 Toggle, PB09 Toggle
}

void swEvent() {
  EVSYS->SWEVT.reg = 0x80000001; //SWEVENT CH31 & CH0
}

void dumpPortEvent() {
  for (int n=0; n<4; n++) {
    Serial.printf("Port G[%d]=0x%08x \r\n", n, PORT->Group[n].EVCTRL.reg);
  }
}

void dumpEvsysCHANNELs() {
  for (int n=0; n<32; n++) {
    Serial.printf("Evsys CH[%d]=0x%08x \r\n", n, EVSYS->Channel[n].CHANNEL.reg);
  }
}

void dumpEvsysUSERPORTs() {
  for (int m=1; m<5; m++) {
    Serial.printf("Evsys PortG[%d]=%d \r\n", (m - 1), (EVSYS->USER[m].bit.CHANNEL - 1));
  }
}

void setup() {
  Serial.begin(9600);
  while(!Serial);
  Serial.printf("ATSAMD51 EVSYS test sample.\r\n");
  pinMode(GPIO27, OUTPUT);
  pinMode(GPIO22, OUTPUT);
  pinMode(GPIO13, OUTPUT);
  pinMode(GPIO26, OUTPUT);
  digitalWrite(GPIO27, 1);
  digitalWrite(GPIO22, 0);
  digitalWrite(GPIO13, 1);
  digitalWrite(GPIO26, 0);
  setupPortEvent(); // Enable PB08 port event
  setupEVSYS();
  dumpEvsysUSERPORTs();
  dumpPortEvent();
  counter = 0;
}

void loop() {
  Serial.println(counter++);
  swEvent();
  delay(1000);
}