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

Joseph Halfmoon

前回はDACでアナログ波形を垂れ流してみました。ATSAMD51のDACにはいろいろ近代的な機能がありつつも最初は一番単純な方法で実験。今回はADCです。これまた近代的な機能がいろいろあるのです。しかしこちらも最初は単純な方法でまずは動かしてみます。ADCで測った波形をそのままDACに出力。サンプリング周波数は10kHz。

(実験に使用したソース全文は末尾に)

別シリーズにて、STM32F446REや、ラズパイPico(RP2040)で ADコンバータで入力したアナログ波形をそのままDAコンバータで出力、というパターンをやってきております。今回は、Microchip社のArm Cortex-M4搭載マイコン ATSAMD51P19A を使って同じようなことをしてみます。

プログラムとしては、前回DACの実験で使用したものに、ADCのセットアップ関数と読み取り関数を加え、前回適当なパターンでDAC出力していたのをADCで読み取った外部のアナログ波形の情報を出力するように変更しただけのものです。

ハードウエアの設定状況確認

ATSAMD51はADコンバータを2個搭載しています。前回のDAコンバータも2個登載でしたが、基本の制御レジスタは2個でシェアされていました。これに対してADコンバータの方は、「0」と「1」がほぼ独立したレジスタセットをそれぞれもっているスタイルです。今回は adc0 の方を使って実験してみます。

また、ADコンバータでアナログ電圧を測定可能な端子は多数あり、アナログマルチプレクサで切り替えて測定できます。オンチップの温度センサやDAコンバータ出力、各種電源をスケールした内部電圧などもアナログ入力に接続可能で、ADCは内部のモニタリングにも使用されます。

まずは、スタートアップルーチン通過後のADC関連のハードウエアの設定を確かめておきます。チェックに使ったのは以下の関数です。

void checkADCsetting() {
  uint32_t gclkADC = GCLK->PCHCTRL[40].reg; //ADC0=40, ADC1=41, bit6: CHEN, bit3-0: GEN
  uint32_t mclkADC = MCLK->APBDMASK.bit.ADC0_; 
  uint32_t portMUX = PORT->Group[1].PMUX[4].reg; //bit7-4: PB09, bit3-0: PB08, Function B(0x1)=Analog
  Serial.printf("ADC GCLK=0x%08x MCLK=%d PORT=0x%08x\r\n", gclkADC, mclkADC, portMUX);
}

ADC0 を使うためにはまず以下の2つのクロックが供給されていなければなりません。

  1. GCLK_ADC0
  2. CLK_APB_ADC0

第1のGCLK_ADC0は、AD変換動作そのものに使用されるクロックです。12ビットのAD変換を行うためには、12クロック+サンプリングに使うクロック数など加える、といった形でこのクロックを使います。SAMD51のADの最高速は1Mspsとなっています。最高速を出すためには32MHz以上のクロックを与えねばなりません。

第2のCLK_APB_ADC0は、CPUがADC内のレジスタの読み書きに使うクロックです。これが供給されていないとレジスタ操作ができませぬ。

上記の確認用の関数によって調べたところ、GCLK_ADC0には 48MHz のクロック(USBに供給されているものと同じ)が供給されていました。また、APBクロックもイネーブルになっていました。

今回、ADした内容をDAにそのまま出力ということで使用する端子のアサインは以下のようにいたしました。Wio Terminalの裏側のRapberry Pi互換配列の拡張端子から「並び」で信号を取り出しやすいものを選びました。

  • AD入力、チップのPB08端子、ADC0のAIN[2]
  • DA出力、チップのPA02端子、DAのVOUT0
  • AD、DAともに参照電圧端子、チップのPA03端子、VREFA

前回のDAのセッティングで下の2端子は使用可能となっています。今回追加で設定しなければならないのは上記のPB08端子のみです。

ADC0の設定

末尾に掲げたソース全文の中で、setupADC0()関数で設定しているのは以下です。最初にSoftware RESETかけてデフォルト状態を担保しており、なるべくデフォルトのままで使っているので、明示的に設定している部分は少ないです。

  • シングル・コンバージョンモード
  • 12ビット精度
  • コレクション、割り込み、DMA等不使用
  • GCLK_ADC0の48MHzを4分周して使用
テスト用ハードウエアの接続

前回につづき、Wio TerminalのGPIO22端子(チップのPB09, Wio TerminalのA1/D1)はソフトウエアの「ハートビート信号」ということでLEDを接続してあります。ソフトが動いていれば毎秒の点滅。

GPIO27端子(PB08)が今回メインのADC0の入力です。これには外部のAnalogDiscovery2の波形ジェネレータからアナログ波形を印加します。また、GPIO17(PA02)は前回同様DACの出力です。10kHzのサンプリングクロックで測定したアナログ波形をそのままの周波数で出力しています。前回同様「階段状」の波形になって出力される筈。

接続の様子が以下に。
ADDA_DUT

動作結果

アナログ入力に オフセット 500mV、振幅200mVの1kHzの正弦波を与えてみたときの様子が以下です。黄色が入力波形、青色がDACの出力波形です。グラフの右地に測定結果の数字も出してあります。

ADDA_1kHzDAC出力にはLPF的なものは挿入していないので、この速さだと「ちゃんと」階段状に出力されているのが分かります。しかし、振幅などはほぼOKかな。

これでATSAMD51でも他のマイコンでやったのと同じようなことができました。そのうちATSAMD51のAD/DA特有の「近代的な」機能を使って、これをブラッシュアップしてみたいと思っているのですがね。いつになるんだか。

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

IoT何をいまさら(99) ATSAMD51、DMAで1バイト転送成功までの長い道 へ進む

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

プラットフォームは atmelsam、ターゲットボードは SeeedStudio製Wio Terminal、フレームワークはArduinoのソースです。VS Code + PlatformIO環境でビルドして動作確認しています。

#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;

void setupADC0() {
  PORT->Group[1].PMUX[4].bit.PMUXE = 0x1; // bit3-0: PB08, Function B(0x1)=Analog
  PORT->Group[1].PINCFG[8].bit.PMUXEN = 1; //PB08 Analog
  ADC0->CTRLA.bit.ENABLE = 0; // Disable whole ADC0 to access enable-protected registers.
  while(ADC0->SYNCBUSY.bit.ENABLE == 1) {}
  ADC0->CTRLA.bit.SWRST = 1; // Software reset
  while(ADC0->SYNCBUSY.bit.SWRST == 1) {}
  ADC0->CTRLA.bit.PRESCALER = 0x1; // DIV4
  ADC0->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_AREFA;
  ADC0->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN2; //ADC0-AIN2 = PB08
  ADC0->SAMPCTRL.bit.SAMPLEN = 4; // 4 clocks for sampling.
  ADC0->CTRLA.bit.ENABLE = 1; // Enable ADC0
  while(ADC0->SYNCBUSY.bit.ENABLE == 1) {}
}

uint16_t inputADC0() {
  while(ADC0->STATUS.bit.ADCBUSY == 1) {} // wait while ADCBUSY.
  ADC0->INTFLAG.bit.RESRDY = 1; // Clear RESRDY flag.
  ADC0->SWTRIG.bit.START = 1; // Start ADC0 conversion.
  while(ADC0->INTFLAG.bit.RESRDY == 0) {} // wait for the conversion result.
  uint16_t result = ADC0->RESULT.reg;
  ADC0->INTFLAG.bit.RESRDY = 1; // Clear RESRDY flag.
  return result;
}

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->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 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) {
  dval = inputADC0();
  outDAC0(dval);
  TC7->COUNT8.INTFLAG.reg = 0x33;
}

void setup() {
  Serial.begin(9600);
  while(!Serial);
  Serial.printf("ATSAMD51 ADDA test.\r\n");
  pinMode(GPIO22, OUTPUT);
  digitalWrite(GPIO22, 0);
  counter = 0;
  dval = 0;
  setupDAC0();
  setupADC0();
  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 : DVAL=%d\r\n",counter, dval);
  delay(1000);
}