がれすたさんのDIY日記

電子回路、Python、組み込みシステム開発、自作エフェクターを語るblog

MIMXRT10xxのADC External Trigger Controlを使ってみる(software trigger編)

概要

今回はMIMXRT10xxのExternal Trigger Controlについてやっていくことにする。

ADC External Trigger Control(以下ADCETC)は下図に示すようにADCモジュールと他のペリフェラルをつなげてコアの介在なしにデータ転送(DMA使用)したりトリガ変換したりできる便利なやつです。
f:id:gsmcustomeffects:20191128013713p:plain

Ref Manualによれば

The ADC_ETC module enables multiple users share the ADC modules in a TIMEDivision-Multiplexing (TDM) way.
The external triggers can be from Cross BAR(XBAR) and TSC in SOC.
The ADC_ETC has two channels, each channel can support one TSC and four external triggers from XBAR to one ADC module.
The ADC_ETC can support interrupt mode and DMA mode.

まとめるとTDM方式で他のモジュールがADCモジュールを使用可能でありTouch Screen Controller (TSC)とCross BAR(XBAR)からトリガ可能である。
割り込みとDMAをサポートしているということ。

通常のADCモジュールではCPU-ADC間でデータのやり取りをするがADC ETCを利用するとXBARでつながっているペリフェラルとADCを連動できる。
その他ADCチェインという機能も使えて複数ピンを読んだりするとき設定しておくといい感じに捌いてくれる。

テスト環境と留意事項

  • MCUXpresso IDE 11.0.1
  • MCUXpresso SDK 2.50(ここは使う環境によって変わる)
  • MIMXRT1020-EVK(ここは使う環境によって変わる)

今回はNXP公式ボードのMIMXRT1020-EVKを使うことにする。

今回の内容は前回のADCの記事を読んでADCモジュール単体での使い方を理解している人向けなので注意してください
またペリフェラルツールを多用するのでその辺の使い方がわからない人はブログの一番下の参考文献に貼ってある記事を読んでください

software trigger

まずはsoftware triggerからやっていく。
ADC_ETC software triggerはpolling APIで使用するのと何が違うのか?

polling APIの場合はADCモジュールのコンフィグを行い変換完了後レジスタを読みに行く。
複数チャンネル読むには本数分その動作を行わなければならない。

一方でADC_ETCのソフトウエアトリガ機能を使うとソフト上からハードトリガ信号を出してコンバージョンを開始する。
加えてチェイン機能というものがあり、あらかじめ登録したチェインに従って連続で変換を行ってくれる。
指定チャンネルの変換が終わるとそれぞれ別々のレジスタから値を持ってくることができ毎度毎度データレジスタをケアしなくていい。

ここでADCモジュールに対してはハードトリガであること、ソフト側はETCをかえしてADCを操作していることことに注意。

f:id:gsmcustomeffects:20191210084314p:plain

次の項から新規プロジェクトからADC_ETCを使用する流れを説明していく。

プロジェクトの作成

通常通りプロジェクトを作成します。
ADC,ADC_ETCをインポートを忘れずに行ってください
f:id:gsmcustomeffects:20191212002109p:plain

ピンの設定

まずはGPIOのオルタネート設定でこのピンをADCとして使うというのを設定してあげます。

ピン設定ツールを開いてADCのピンを設定する。
今回はIN13,IN12,IN11,IN10を使うのでそれを設定する。
f:id:gsmcustomeffects:20191211010548p:plain

評価ボードでいうArduinoヘッダピンを使っている。
f:id:gsmcustomeffects:20191211011549p:plain

ADCの設定

次にADCモジュールの設定をしていく

f:id:gsmcustomeffects:20191211021039p:plain
ADCの設定例(画像が小さくて見れない方はクリックして拡大

ADC_ETCの設定

次にADC_ETC側の設定をしていく

f:id:gsmcustomeffects:20191211025625p:plain
ADC_ETCの設定例(画像が小さくて見れない方はクリックして拡大

設定が終わったら上部のupdate codeをクリックしてコードをジェネレートしてください。
そうするとperipherals.cにADC、ADC_ETCの初期化コードが出力されるはずです。

ここで出力コードにバグがあるので一部修正します。

/*
const adc_etc_trigger_chain_config_t ADC_ETC_1_TC_0_chain_config[4] = {
  {
    .enableB2BMode = true,
    .ADCHCRegisterSelect = 0,
    .ADCChannelSelect = 13,
    .InterruptEnable = kADC_ETC_InterruptDisable
  },
  {
    .enableB2BMode = true,
    .ADCHCRegisterSelect = 1,
    .ADCChannelSelect = 12,
    .InterruptEnable = kADC_ETC_InterruptDisable
  },
  {
    .enableB2BMode = true,
    .ADCHCRegisterSelect = 2,
    .ADCChannelSelect = 11,
    .InterruptEnable = kADC_ETC_InterruptDisable
  },
  {
    .enableB2BMode = true,
    .ADCHCRegisterSelect = 3,
    .ADCChannelSelect = 10,
    .InterruptEnable = kADC_ETC_Done0InterruptEnable
  }
};
*/

const adc_etc_trigger_chain_config_t ADC_ETC_1_TC_0_chain_config[4] = {
  {
    .enableB2BMode = true,
    .ADCHCRegisterSelect = 1U<<0,
    .ADCChannelSelect = 13,
    .InterruptEnable = kADC_ETC_InterruptDisable
  },
  {
    .enableB2BMode = true,
    .ADCHCRegisterSelect = 1U<<1,
    .ADCChannelSelect = 12,
    .InterruptEnable = kADC_ETC_InterruptDisable
  },
  {
    .enableB2BMode = true,
    .ADCHCRegisterSelect = 1U<<2,
    .ADCChannelSelect = 11,
    .InterruptEnable = kADC_ETC_InterruptDisable
  },
  {
    .enableB2BMode = true,
    .ADCHCRegisterSelect = 1U<<3,
    .ADCChannelSelect = 10,
    .InterruptEnable = kADC_ETC_Done0InterruptEnable
  }
};

修正箇所はADCHCRegisterSelect メンバへの代入部分です。

SDK自体のADCHCRegisterSelect メンバの中まで行くとコメントアウトで解説があります。
f:id:gsmcustomeffects:20191212035334p:plain

要は10進じゃなくビットフィールドで管理しろということなので、10進のまま入れるなら2U,4U,8U...な感じで代入しないといけないということですね。

RMマニュアルをよく読んでフィールドが8ビットであることtriggerが8本あることが頭に入っていないと気付きにくいバグですね。
(むしろユーザーはSDK側で対応されてると思ってしまうでこういうの本当にやめてほしい)

f:id:gsmcustomeffects:20191212035603p:plain
ETC_TRIG Chain 0/1 Register

それとフィールドの説明more informationと書かないでここでしてくれ・・・・・

ユーザー実装コード

最後にユーザーが実装しないといけないコード部分を貼っておく
やってることを箇条書きで示しておく

  • GETCHARして何か入力されるまでwait
  • ADC_ETC_DoSoftwareTriggerをコールしてADCのチェインコンバージョンを開始(0,1,2,3の4つのADC
  • 最後のADCが終わったらADC_ETC_IRQ0_IRQHandlerに飛んでETCの該当データレジスタからデータを取り出す。
  • 割り込みフラグの解除
#include <stdio.h>
#include "board.h"
#include "peripherals.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "MIMXRT1021.h"
#include "fsl_debug_console.h"
/* TODO: insert other include files here. */

/* TODO: insert other definitions and declarations here. */

/*
 * @brief   Application entry point.
 */
volatile uint32_t g_adc_value[4]={0};
volatile bool g_AdcConversionDoneFlag;

void ADC_ETC_IRQ0_IRQHandler(void)
{

    g_AdcConversionDoneFlag = true;
    g_adc_value[0] = ADC_ETC_GetADCConversionValue(ADC_ETC, 0U, 0U); /* Get trigger0 chain0 result. */
    g_adc_value[1] = ADC_ETC_GetADCConversionValue(ADC_ETC, 0U, 1U); /* Get trigger0 chain1 result. */
    g_adc_value[2] = ADC_ETC_GetADCConversionValue(ADC_ETC, 0U, 2U); /* Get trigger0 chain2 result. */
    g_adc_value[3] = ADC_ETC_GetADCConversionValue(ADC_ETC, 0U, 3U); /* Get trigger0 chain3 result. */
    ADC_ETC_ClearInterruptStatusFlags(ADC_ETC, kADC_ETC_Trg0TriggerSource, kADC_ETC_Done0StatusFlagMask);
    __DSB();
}


int main(void) {

  	/* Init board hardware. */
    BOARD_InitBootPins();
    BOARD_InitBootClocks();
    BOARD_InitBootPeripherals();
  	/* Init FSL debug console. */
    BOARD_InitDebugConsole();

    PRINTF("Hello World\n");

    /* Force the counter to be placed into memory. */
    /* Enter an infinite loop, just incrementing a counter. */
    while(1) {
    	g_AdcConversionDoneFlag = false;
		PRINTF("Press any key to get user channel's ADC value.\r\n");
		GETCHAR();
		ADC_ETC_DoSoftwareTrigger(ADC_ETC, 0U); /* Do software XBAR trigger0. */

		while (!g_AdcConversionDoneFlag)
		{
		}
		PRINTF("ADC conversion value is %d,%d,%d,%d\r\n", g_adc_value[0],g_adc_value[1],g_adc_value[2],g_adc_value[3]);
	}
}

PRINTFに関してはUARTとリンクしてもいいしsemihostとリンクしてもいいと思う。
NXP実装のGETCHARは微妙に調子が悪いのでメインの待ち部分はSystickタイマーとかPITとかGPT使うといい感じに連続コンバージョンできると思う。

まとめ

ADC、ADC_ETCを使うことでETCソフトトリガーで複数チャンネルを変換しPRINTFすることができた。
この例ではトリガー1本のみ使用、ADC1のみの使用なので凝った例ではないがNXP提供のサンプルを含めADC関連の理解に貢献出来たらうれしいと思う。

f:id:gsmcustomeffects:20191212040919p:plain
MCUXpressoIDEの変数グラフ表示機能でADCをみている図

MEMO

途中で出てきたインターバルについて解説してなかったので
f:id:gsmcustomeffects:20191212041700p:plain

応用的なやつ

f:id:gsmcustomeffects:20191212042309p:plain