Arduino M0をもっと使いこなす

最近、Arduino M0を購入しました。
Arduino.orgのものは何気に初めてだったりします。
Arduino Zero, Arduino M0 Proとほとんど変わりません(EDBGの有無)

いじってみようと思って色々ネットで探そうとするも資料があまり転がってないという。
利用者が少ないんでしょうか。
欠点として入力トレラント機能がなかったり、最大出力電流が小さかったりするのですが。
それでも私は高性能なArduino M0をもっと使ってやりたいのです。

スポンサーリンク

基本的にArduino UnoなどとはMCUがまったく違うのでアーキテクチャも異なります。
そのためライブラリが動かなかったり専用になってしまうところもあるそうで・・・
それでもM0は工夫すれば結構便利になるよということを紹介したいと思います。

また、今回紹介しているものはすべてArduino IDE 1.7.7(Arduino.org)で検証しています。
Arduino IDE 1.6系(Arduino.cc)では書き込みもできませんし、記述も異なります。
やはりArduino Zeroとはちょっと違うようです。

pinMode, digitalWriteの高速化

Arduino Unoでも紹介しましたが、M0でも高速化できます。

クロック周波数はUnoの3倍の時点で速いですが、その効果をもっと実感できるようになるでしょう。
では、実際の変更したLチカのコードを見てみましょう。

void setup() {
  REG_PORT_DIRSET0 = PORT_PA17;
}

void loop() {
  REG_PORT_OUTSET0 = PORT_PA17;
  delay(1000);
  REG_PORT_OUTCLR0 = PORT_PA17;
  delay(1000);
}

setup関数内にある1行はpinMode(13, OUTPUT);と同じです。
これはpinMode関数で書いたほうがわかりやすいかもしれませんが。

代入式の右辺はArduino M0のMCU(SAMD21G18A)のポート直打ちです。
そのため回路図からどのポートを使うか調べる必要があります。
PORAT_PXNNみたいな感じでポートを指定します。

loop関数内の1行目が、digitalWrite(13, HIGH);に対応します。
右辺はpinModeと同様です。

loop関数内の3行目が、digitalWrite(13, LOW);に対応します。
代入式の左辺でポートのHIGH, LOWを決めます。
Arduino Unoなどの高速化の仕方が全く違いますね。

検証

どれくらい速くなるのか検証してみます。
比較するプログラムはArduino Unoの時と同じです。
setup関数は実際には書いていますが省略しています。

void loop() {
  digitalWrite(13, HIGH);
  digitalWrite(13, LOW);
}
void loop() {
  REG_PORT_OUTSET0 = PORT_PA17;
  REG_PORT_OUTCLR0 = PORT_PA17;
}

上の普通のコードは300kHzくらいになりました。
通常の波形

一方、下の直打ちコードはというと。
高速化
周波数は1.6MHz程度になっています。
Unoの3倍というわけにはいかないですけど、高速化はできています。

シリアル通信(UART)の増設

M0ではシリアル通信はSerialUSBとSerial5が使えますが、SoftwareSerialライブラリが使えません。
これ以上増やせないのはちょっと不便なときも出てくるのではないでしょうか。
でも実はM0のコアはシリアル通信を複数チャンネル使えるようになっているんです。
なんとあと2つ追加できるようです、すごいですよね。
まだIDEで実装されていないだけかもしれませんが。

Serial2

まずはSerial2というのを作ったスケッチです。

#define PIN_SERIAL2_RX 39  // D12
#define PIN_SERIAL2_TX 41  // D10

// Instantiate the Serial2 class
Uart Serial2(&sercom1, PIN_SERIAL2_RX, PIN_SERIAL2_TX);

void setup() 
{
  Serial2.begin(115200);
}

void loop() 
{  
  if (Serial2.available()) 
  {
    byte byteRead = Serial2.read(); 
    Serial2.write(byteRead);
  }
}

void SERCOM1_Handler()    // Interrupt handler for SERCOM1
{
  Serial2.IrqHandler();
}

使えるようにするにはsetup関数より上にある3行の記述(コメントは含みません)と下にあるSERCOM1_Handerという関数になります。
どこのピンで使えるかというと、12ピンがRX(受信)10ピンがTX(送信)となります。

ただ10ピンと12ピンはSPIにも使われていますので、SPI通信を犠牲にしてしまうことになります。

Serial3

今度は先と同じようにSerial3というものを使えるようにします。
こちらのほうがSPI通信を犠牲にしないのでおすすめです。
ただ、もう1つ使えるようにするには次のファイルを編集する必要があります。

IDEのインストール先\hardware\arduino\samd\variants\arduino_zero\variant.cpp

このファイルの中のPinDescription g_APinDescription[]という配列の最後に以下の記述を加えます。

// 48..49 - SERCOM2
 { PORTA, 14, PIO_SERCOM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // SERCOM2/PAD[2]
 { PORTA, 15, PIO_SERCOM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // SERCOM2/PAD[3]

書き換えたらSerial2と同じような記述で使えるようになります。
また、5ピンがRX(受信)4ピンがTX(送信)となります。

#define PIN_SERIAL3_RX 49  // D5
#define PIN_SERIAL3_TX 48  // D4

// Instantiate the Serial3 class
Uart Serial3(&sercom2, PIN_SERIAL3_RX, PIN_SERIAL3_TX);

void setup() 
{
  Serial3.begin(115200);
}

void loop() 
{  
  if (Serial3.available()) 
  {
    byte byteRead = Serial3.read(); 
    Serial3.write(byteRead);
  }
}

void SERCOM2_Handler()    // Interrupt handler for SERCOM2
{
  Serial3.IrqHandler();
}

ちなみにこれと同じような感じでSPIやI2Cも複数チャンネルにできるそうです。
確認できればまた紹介したいと思います。

タイマー割り込み

一応使えるみたいですが、ライブラリになっていません。
ちょっと長いです。
以下、タイマー割り込みを使ったLチカです。

int pin_ovf_led = 13;
unsigned int irq_ovf_count = 0;

void setup() {

  pinMode(pin_ovf_led, OUTPUT);
  digitalWrite(pin_ovf_led, LOW);
  
  REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TCC0_TCC1)) ;
  while ( GCLK->STATUS.bit.SYNCBUSY == 1 );

  Tcc* TC = (Tcc*) TCC0;
  
  TC->CTRLA.reg &= ~TCC_CTRLA_ENABLE;
  while (TC->SYNCBUSY.bit.ENABLE == 1);

  TC->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1024;  // Set perscaler

  TC->WAVE.reg |= TCC_WAVE_WAVEGEN_NFRQ;
  while (TC->SYNCBUSY.bit.WAVE == 1);

  TC->PER.reg = 0xB71B;  // Set counter Top using the PER register
  while (TC->SYNCBUSY.bit.PER == 1);

  TC->CC[0].reg = 0xFFF;
  while (TC->SYNCBUSY.bit.CC0 == 1);
  
  TC->INTENSET.reg = 0;
  TC->INTENSET.bit.OVF = 1;
  TC->INTENSET.bit.MC0 = 1;

  NVIC_EnableIRQ(TCC0_IRQn);

  TC->CTRLA.reg |= TCC_CTRLA_ENABLE ;
  while (TC->SYNCBUSY.bit.ENABLE == 1);

}

void loop() {
}

void TCC0_Handler()
{
  Tcc* TC = (Tcc*) TCC0;
  if (TC->INTFLAG.bit.OVF == 1) {
    // do something
    digitalWrite(pin_ovf_led, irq_ovf_count % 2);  // for blink led
    irq_ovf_count++;                               // for blink led
    TC->INTFLAG.bit.OVF = 1;
  }
  
  if (TC->INTFLAG.bit.MC0 == 1) {
    TC->INTFLAG.bit.MC0 = 1;
  }
}

長ったらしいですが、とりあえず注目すべきはTC->PER.regに代入する値です。
ここの値で呼び出される間隔を変えることができます。
1ms単位では扱えず、これだとおそらく約21.3us間隔です。(本当かどうかはわかりません)
TCC_CTRLA_PRESCALER_DIV1024という定数をTCC_CTRLA_PRESCALER_DIV256にすると(128, 512はありません、64はあります)扱える秒数は短くなりますが、より細かく秒数を設定できます。

行いたい処理はTCC0_Handler関数の中にあるTC->INTFLAG.bit.OVF == 1が条件のif文の中に書きます。
また、PWMは2, 3, 4, 5, 6, 7ピンが使えなくなります。

Arduino DueのScheduler(スケジューラ)ライブラリを移植

Arduino Dueではスケジューラというライブラリでマルチタスクを可能にしています。

マルチタスクが使えるとだいぶ幅が広がりますよ!
ただ、どこまで複雑にしても使えるのかが心配です、SRAM結構食いそうですしね。
Unoでタイマー割り込みで処理を分けてた頃が懐かしくなるかもしれません。

追記(2015/11/28)

Arduino IDE 1.6系のスケジューラライブラリがZeroに対応したそうなので、それに置き換えてみるとDueでもZeroでも使えました。
使い分けをしなくてもいいのでそちらに置き換えることをおすすめします。

アナログ入力について

これはただの注意事項ですが、Arduino M0はADCの基準電圧がデフォルトでは1.65Vになっています。
電源電圧(3.3V)を基準にするにはanalogReference(AR_DEFAULT);とsetup関数に書く必要があります。
ちなみにAREFピンにかかる電圧を基準にするにはEXTERNALではなくてAR_EXTERNALです。
AREFピンには最大2.7Vしかかけられません。
同じようにINTERNALはAR_INTERNALで基準電圧は1.0Vになります。(Unoは1.1V)

Arduino M0はDueと同様、analogReadResosution関数によってアナログ入力の分解能を変更することができ、8ビット、10ビット、12ビットに対応しています。
Arduino M0のデフォルトは10ビットになっています。


その他にPWMの周波数を変更するのもできるようですが、なぜかうまくいきません。
また、便利になるようなものがあれば追記していきます。

スポンサーリンク

Leave a Comment