IoT何をいまさら(97) ATSAMD51、DACを使う#1、Wio Terminal

Joseph Halfmoon

Microchip社のArm Cortex-M4搭載マイコン、ATSAMD51の周辺回路をなるべくダイレクトに制御してみるシリーズ、今回はDAC(Digital to Analog コンバータ)であります。データシートを読んでみると信号処理向け支援機能なども内蔵しておりなかなか強力。とりあえず今回は簡単にタイマ割り込みハンドラの中でソフトでDA出力してみました。

(実験に使用したソース全文は末尾に。SeeedStudio社のWio Terminalがターゲットボードです。)

DACの設定状況を確認

Wio TerminalのStart upルーチン内でいろいろ初期設定がなされているので、まず、DAC周りの設定がどうなっているのか確認してみました。確認に使用したのは以下の関数です。

void checkDACsetting() {
  uint32_t gclkDAC = GCLK->PCHCTRL[42].reg; //bit6: CHEN, bit3-0: GEN
  uint32_t mclkDAC = MCLK->APBDMASK.bit.DAC_;
  uint32_t portMUX = PORT->Group[0].PMUX[1].reg; //bit7-4: PA03, bit3-0: PA02, Function B(0x1)=Analog
  Serial.printf("DAC GCLK=0x%08x MCLK=%d PORT=0x%08x\r\n", gclkDAC, mclkDAC, portMUX);
}

DACには以下2種類のクロックが供給されていないと動作できないのですが、その設定は以下のようになっていました。

  1. DAC本体の変換をつかさどるGCLK_DACはイネーブル。12MHzクロックが供給されていた。
  2. APBバス経由のCPUの読み書きのクロックである CLK_DAC_APBもイネーブル

ATSAMD51のDACは、GCLK_DACクロックの24サイクルで1回のDAを行えるようになっているので、1の設定であると 500kHz相当の変換速度(サンプリング周波数の上限は1Msps)ということになります。

2の設定はイネーブルになっていれば読み書きOKです。ただ、手元にダウンロード済のMicrochip社のデータシートを見ていると、APBD Maskレジスタのビット9、DACへのAPBクロックをイネーブルにするビットは「R」、つまりリードオンリと書かれています。その上RESET時初期値は0です。他の周辺回路については皆「RW」となっていて、実際に1を書けばイネーブル、0をかけばディセーブルできるのです。DACだけRってなんでだろ~。データシートに疑いの目が向いてしまいます。今回はスタートアップルーチンでイネーブルになっていたので何もしませんでしたが、イネーブルにするにはどうしたら良いの?不思議だ。

また、DAコンバータの出力および参照電圧端子については未初期化でした。他のペリフェラルと異なり、複数の端子から1つ選ぶという選択は無いのです。しかし、端子を、使う、使わないの選択はあるので、設定されていないようです。

ポート関係が未設定だったので、DACをセットアップする際には、DACのレジスタに加えてPORTのレジスタも設定しないと信号が取り出せませぬ。

DACの設定

ATSAMD51の場合、周辺装置としてのDACは1つですが、2つのチャネルを搭載しています。今回は、Wio Terminalに取り付けてある治具のために、たまたま信号が取り出しやすかった、という理由だけでDAC0チャネルを利用です。なお、DAC0は外部にDAした電圧を出力するだけでなく、内蔵アナログコンパレータの比較用電圧としても接続可能です。設定は、DAC全体の設定に関わる部分と、各チャネル毎の部分に分かれます。また、ATSAMD51の他の多くのペリフェラル同様、以下に注意しておかないとなりません。

  1. 多くのレジスタが “enable-protected” なのでまずDACをディセーブルにしてから設定を操作しないとならない。全部終わってからイネーブル。
  2. GCLKとMCLKの2種クロックが使われているので、多くのレジスタでクロック間の同期待ちが必要

実際の設定コードは、末尾掲載のソースの setupDAC0()関数の中にまとめてあります。今回はDACのDATAレジスタにソフトウエアで12ビットの数値(右詰め)を書き込んだら24GCLK_DACサイクル後に端子に出力される、というミニマムな設定です。設定手順としては以下の通り。

  1. ポート、PA03(DAの参照電圧)とPA02(DAC0の出力)をアナログ端子に設定
  2. DAC全体をディセーブルとする
  3. DACにソフトウエアリセットをかけて既知の状態にする(ほぼほぼこのデフォルト値のまま使っている。)
  4. その上でDAC0をイネーブルにするなどの最小限の設定をする
  5. 最後にDAC全体をイネーブルとする

ちょっといぶかしかったのは、DACの参照電圧端子です。手元のWio Terminalの回路図からはDAの参照電圧端子PA03(VREFA)とアナログ電源VDDANA端子に同じ電圧が加わるような回路に見えるのです。しかし、DACの参照電圧をVDDANAと指定すると出力が0Vに張り付いてしまいました。VREFAを指定すれば正常に動作します。なんでだろ~

動作確認

DAC0出力はGPIO17端子です。前回作成したTC7の割り込みハンドラを10kHzで動作させておきます。そして割り込み発生毎に、DACの出力可能電圧範囲を8等分するような電圧を階段状に出力させています。10kHzのサンプリングクロックが外部から確認できるように、割り込み発生の度にGPIO27端子もトグルさせています。また、ソフトウエアが「死んでいない、動作中」を示すためにGPIO22端子をLEDと接続し、表のループで毎秒点滅させています。

例によって動作確認には Analog Discovery2使用です。測定しているところがこちら。

DACdut実際に動作している波形は以下に。黄色がDAC0の出力、青がタイミングを示すGPIO27のトグルです。100μ秒毎にDAC0の値が変わっているのが見えると思います。

DACoutputとりあえず一番簡単な方法でDACを動かしてみました。SAMD51のDACはまだまだ面白そうな機能があるのです。しかし、とりあえずDACは一端おいて、次回はADCを動かしてみたいとおもいます。

IoT何をいまさら(96) Wio Terminal、困ったときの uf2。TC割り込み へ戻る

IoT何をいまさら(98) ATSAMD51、ADからDA直接、Wio Terminal へ進む

実験に使用したソース全文
#include <Arduino.h>

// GPIO17 (WIO:DAC0, SAMD51:PA02<VOUT0>)
// ---    (WIO:VCC3V3MCU_A, SAMD51:PA03<VREFA>)
// GPIO27 (WIO:A0/D0, SAMD51:PB08, Arduino:0) 
// GPIO22 (WIO:A1/D1, SAMD51:PB09, Arduino:1) 
#define GPIO27  (0)
#define GPIO22  (1)
// TC7 period value
//  17 ... 44.118kHz
//  75 ... 10kHz
// 255 ... 2.94kHz
#define PERIOD (75)

uint32_t counter;
volatile uint16_t dval;
volatile uint32_t flag;

void setupDAC0() {
  PORT->Group[0].PMUX[1].reg = 0x11; //bit7-4: PA03, bit3-0: PA02, Function B(0x1)=Analog
  PORT->Group[0].PINCFG[2].bit.PMUXEN = 1; //PA02 Analog
  PORT->Group[0].PINCFG[3].bit.PMUXEN = 1; //PA03 Analog
  DAC->CTRLA.bit.ENABLE = 0; // Disable whole DAC to access enable-protected registers.
  while(DAC->SYNCBUSY.bit.ENABLE == 1) {}
  DAC->CTRLA.bit.SWRST = 1;  // Software reset
  while(DAC->SYNCBUSY.bit.SWRST == 1) {}
//  DAC->CTRLB.reg = DAC_CTRLB_REFSEL_VDDANA; // VREF=Volatage supply and Single mode
  DAC->DACCTRL[0].bit.RUNSTDBY = 1;
  DAC->DACCTRL[0].bit.REFRESH = 0x3;  // Refresh every 90uS
  DAC->DACCTRL[0].bit.ENABLE = 1; // Enable DAC0
  DAC->CTRLA.bit.ENABLE = 1; // Enable DAC
  while(DAC->SYNCBUSY.bit.ENABLE == 1) {}
}

void outDAC0(uint16_t dacDat) {
  while(DAC->SYNCBUSY.bit.DATA0 == 1) {}
  DAC->DATA[0].reg = dacDat;
}

void dacStatus() {
  Serial.printf("DAC STATUS: %01x\r\n", DAC->STATUS.reg);
}

// TC7: 8bit interval timer
void setupTC7(uint8_t periodValue) {
  MCLK->APBDMASK.bit.TC7_ = 1;      // Enable CLK_TC7_APB
  GCLK->PCHCTRL[39].reg = 0x41;     // GCLK_TC7 <= Generator 1 (48MHz)
  NVIC->ISER[3] |= 0x40000;         // Enable TC7 Interrupt #114 [3] bit 18
  TC7->COUNT8.CTRLA.bit.ENABLE = 0; // Disable TC7 (Some TC7 registers are enable-protected.)
  while(TC7->COUNT8.SYNCBUSY.bit.ENABLE == 1) {}
  TC7->COUNT8.CTRLA.bit.SWRST = 1;  // TC7 Software reset for initialize
  while(TC7->COUNT8.SYNCBUSY.bit.SWRST == 1) {}
  TC7->COUNT8.CTRLA.reg = 0x00000504; // DIV64, COUNT8
  TC7->COUNT8.INTFLAG.reg = 0x33;     // Clear all interrupt flags
  TC7->COUNT8.INTENSET.bit.OVF = 1; // Enable TC7 OVF interrupt
  TC7->COUNT8.PERBUF.reg = periodValue;
  TC7->COUNT8.CTRLA.bit.ENABLE = 1; // Enable TC7
}

void TC7_Handler(void) {
  flag = (flag == 0) ? 1 : 0;
  digitalWrite(GPIO27, flag);
  dval += 512;
  if (dval > 4095) {
    dval = 0;
  }
  outDAC0(dval);
  TC7->COUNT8.INTFLAG.reg = 0x33;
}

void setup() {
  Serial.begin(9600);
  while(!Serial);
  Serial.printf("ATSAMD51 DAC test.\r\n");
  pinMode(GPIO27, OUTPUT);
  pinMode(GPIO22, OUTPUT);
  flag = 1;
  digitalWrite(GPIO27, flag);
  digitalWrite(GPIO22, 0);
  counter = 0;
  dval = 0;
  setupDAC0();
  digitalWrite(GPIO22, 1);
  setupTC7(PERIOD);
}

void loop() {
  if ((counter++ % 2) == 0) {
    digitalWrite(GPIO22, 0); // monitor LED ON 
  } else {
    digitalWrite(GPIO22, 1); // monitor LED OFF
  }
  Serial.printf("%d\r\n",counter);
  dacStatus();
  delay(1000);
}