2章 analogReadを
読んでみよう!

analogReadはどこにある?

本章ではanalogReadのソースコードを読み,前章の最後にデフォルト時における1回あたりのanalogReadの時間である約21.39[μs]の割り振りは

だろう,と推察しました.
しかし,この推察が正しければ,「analogReadなんてA/D変換がメインの処理なのに,なんでシステムクロックがたくさん使われているんだ!」,と疑問に思います.
これを解決するにはanalogReadのソースコードを読むしかありません.
そこで,本章の目的はArduino UNO R4で利用されるルネサス社製のマイコンのArduino APIanalogReadのソースコードを読み,analog.cppにある関数analogReadの改良方法を考えます.

ここでいう,ソースコードはあくまでルネサス社製のマイコンで利用するArduino APIのソースコードを指します.そのために,Arduino UNO R3などで利用されるアトメル社製のマイコンで利用するArduino APIのソースコードは対象に含めていませんので注意してください.

Arduino IDEにて,すでにArduino UNO R4を使用した場合,そのパソコンにもArduino APIがインストールされています:

【・・・】はそれぞれユーザやインストール時のバージョンによって異なります.

この中の

が対象となるファイルです.

自身のパソコンのanalog.cppでも,GitHubにあるanalog.cppでもどちらでも構いませんが,GitHub版は最新のファイルに置き換わるため,話が変わるかもしれないことに注意してください.

では,analog.cppを見てみましょう!
analog.cpp内にある関数analogReadのソースコードは2種類あります:

/* ------------------------------------------------------- */
/* FSP PIN NUMBER */
int analogRead(bsp_io_port_pin_t pin) {
/* ------------------------------------------------------- */
  int32_t index = getPinIndex(pin);
  uint16_t cfg_adc = 0;
  ADC_Container *_adc = get_ADC_container_ptr(index, cfg_adc);
  pinPeripheral(pin, (uint32_t) IOPORT_CFG_ANALOG_ENABLE);
  return adcConvert(_adc,cfg_adc);
}

/* ------------------------------------------------------- */
/* LEGACY PIN NUMBER */
int analogRead(pin_size_t pinNumber) {
/* ------------------------------------------------------- */
  int32_t adc_idx = digitalPinToAnalogPin(pinNumber);
  uint16_t cfg_adc = 0;
  ADC_Container *_adc = get_ADC_container_ptr(adc_idx, cfg_adc);
  pinPeripheral(digitalPinToBspPin(adc_idx), (uint32_t) IOPORT_CFG_ANALOG_ENABLE);
  return adcConvert(_adc,cfg_adc);
}

です.
すなわち,analogReadの引数が

の2パターンあることがわかります.
それでは,例えば,analogReadResolution関数のチュートリアルにある
int reading = analogRead(A3);
と書いた場合,どちらのanalogReadが使用されるのでしょうか?
まずは,

から調べていきましょう.

pin_size_t 型とbsp_io_port_pin_t 型

前節では,ルネサス社のマイコンを利用する際のArduino APIの関数analogReadのソースコードには,引数がpin_size_t 型の場合とbsp_io_port_pin_t 型の場合の2種類あることを確認しました.
そもそも,pin_size_tbsp_io_port_pin_t 型とは何でしょうか?
本節では,この二つの型について調べてみましょう.

まずは,Arduino APIにおける関数analogReadの仕様を見てみましょう:
int analogRead(int pin)
と引数は書かれていますね.
APIのため,様々な環境下で使用できるようにintとして統一されていそうですね.
では,pin_size_t 型とbsp_io_port_pin_tとは何でしょうか?

まず,簡単なpin_size_tから紹介します.
pom_size_t型が定義されているのは,Arduino APIのコアであるArduinoCore-APIに含まれるCommon.hです:

#ifdef EXTENDED_PIN_MODE
// Platforms who want to declare more than 256 pins need to define EXTENDED_PIN_MODE globally
typedef uint32_t pin_size_t;
#else
typedef uint8_t pin_size_t;
#endif

すなわち,Arduino UNO R4はピンの数は256ピンよりも少ないため,typedefuint8_tが別名でpin_size_tとなっていることが読み取れます.

続いてbsp_io_port_pin_t 型です.まず,bspとは何か,というと「ルネサス社のボード・サポート・パッケージ(Board Support Package)を意味します.
すなわち,Arduinoが提供しているのではなく,ルネサス社が提供しているマイコンボードを簡単に利用できるようにするためのルネサス APIの一部です.
その中に,bsp_io_port_pin_t 型が列挙型(Enum型)であることが記述されています.
この列挙型では,例えば「IO port 1 pin 2」を指すときに「BSP_IO_PORT_01_PIN_02」という表記を行うことを意味しています.
では,IO port 1 pin 2とは何でしょうか?
久々にルネサス社製のマイコンRA4M1シリーズの「Renesas RA4M1グループ ユーザーズマニュアル ハードウェア編」のp. 423の19. I/Oポート 表19.1を見てみましょう.

表19.1を見てわかるように,パッケージによってピンの数が変わります.
Arduino UNO R4には64ピンのRA4M1 グループ R7FA4M1AB3CFMマイコンが搭載されています.
そのために,合計52本のI/O端子があります.
具体的にはP000などが端子の名前です.
例えば,P010~P015の中にはP014という端子(ピン)があります.
P014はIO port 0 pin 14を意味します.
そのため,P014はbsp_io_port_pin_t 型列挙型では「BSP_IO_PORT_00_PIN_14」となります.
ルネサス社製のマイコンRA4M1のデータシートである新しい資料RA4M1 グループ データシート」のp. 17の図1.5を見てみると実際にP014ピンがどこにあるのかわかります.

さらに,Arduino UNO R4 MinimaのデータシートArduino UNO R4 WiFiのデータシートを見てみるとP014端子がArduino UNOのA0端子に接続されていることが読み取れます.

余談ですが,MinimaとWiFiのデータシートをさらによく見てみると,デジタル入出力の端子であるD2からD13に接続されている端子が違うことに気が付きます.
すなわち,Arduino UNO R4 MinimaとArduino UNO R4 WiFiは(特にレジスタを操作する際には)使い分ける必要があります.
Arduino APIでは,端子の接続,マイコンの違いなどを隠して簡単に使えるようにしてくれているラッパーであることに注意してください.

bsp_io_port_pin_t 型は何か?という話に戻すと,RA4M1マイコンのポートとピンにさせるための列挙型であることがわかりました.
実際のプログラムはルネサス社製のマイコン用Arduino API内のルネサスAPIbsp_io.hにて下記のように列挙型bsp_io_port_pin_tが定義されています(正確にはe_bsp_io_port_pin_t型のtypedefにて定義されています):

typedef enum e_bsp_io_port_pin_t
{
    BSP_IO_PORT_00_PIN_00 = 0x0000,    ///< IO port 0 pin 0
    BSP_IO_PORT_00_PIN_01 = 0x0001,    ///< IO port 0 pin 1
    BSP_IO_PORT_00_PIN_02 = 0x0002,    ///< IO port 0 pin 2
    ....
    ....
    BSP_IO_PORT_14_PIN_13 = 0x0E0D,    ///< IO port 14 pin 13
    BSP_IO_PORT_14_PIN_14 = 0x0E0E,    ///< IO port 14 pin 14
    BSP_IO_PORT_14_PIN_15 = 0x0E0F,    ///< IO port 14 pin 15
} bsp_io_port_pin_t;


次に,いよいよ次に引数がpin_size_t 型の場合とbsp_io_port_pin_t 型の場合のanalogReadのどちらが利用されるのか,確認していきます.
以下,analogReadを確認するためにanalog.cppとanalog.hを改造します.
試される場合は自己責任でお願いします.
改造を試す方は事前にanalog.cppとanalog.hのコピーを作成し,別のフォルダでバックアップを保存することを強くお勧めします.

どっちのanalogReadが使われる?

早速,どちらの関数analogReadが利用されているか,確かめてみるために,analogReadを(自己責任で)改造しましょう.
ロックが掛かっているためArduino IDEからでは,改良できません.
しかし,別のエディタ(Visual Studio Codeなど)でanalog.cppを開き,改良することができます.
まず,手始めに,
bsp_io_port_pin_t型が使われた場合
  Serial.println("bsp_io_port_pin_t");
と表示するようにし,pin_size_t 型が使われた場合
  Serial.println("pin_size_t");
と表示するようにしましょう.
2行しか追加していませんが,念のため全体像を書くと以下のような感じです:

/* ---------------------------------------------------------- */
/* FSP PIN NUMBER */
int analogRead(bsp_io_port_pin_t pin) {
/* ---------------------------------------------------------- */
  Serial.println("bsp_io_port_pin_t");
  int32_t index = getPinIndex(pin);
  uint16_t cfg_adc = 0;
  ADC_Container *_adc = get_ADC_container_ptr(index, cfg_adc);
  pinPeripheral(pin, (uint32_t) IOPORT_CFG_ANALOG_ENABLE); 
  return adcConvert(_adc,cfg_adc);
}

/* ---------------------------------------------------------- */
/* LEGACY PIN NUMBER */
int analogRead(pin_size_t pinNumber) {
/* ---------------------------------------------------------- */
  Serial.println("pin_size_t");
  int32_t adc_idx = digitalPinToAnalogPin(pinNumber);
  uint16_t cfg_adc = 0;
  ADC_Container *_adc = get_ADC_container_ptr(adc_idx, cfg_adc);
  pinPeripheral(digitalPinToBspPin(adc_idx), (uint32_t) IOPORT_CFG_ANALOG_ENABLE);
  return adcConvert(_adc,cfg_adc);
}

続いて,analogReadを使うプログラムをArduino IDEで書いてみましょう:

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  int res = analogRead(A0);
  delay(10000);
}

このプログラムを実行するとanalogReadが使用されるので,どちらのanalogReadが使用されたのかわかります.
その結果

pin_size_t

と出力されたので,LEGACY PIN NUMBERと書かれているpin_size_t 型の関数analogReadが使用されたことがわかりました.

なお,bsp_io_port_pin_t型のanalogReadは2025年2月現在では使い方がわかりません.(bsp_io_port_pin_tを作っても,現状はpin_size_t型に型変換されるため使用できません.)
そのために,pin_size_t 型のanalogReadの解析を次に進めていきましょう!
...と行きたいところですが,早速,digitalPinToAnalogPin(pinNumber)という謎の関数(正確にはマクロ)がてきます.
の中身を読むにはA0やD0などのピン番号(端子番号)についてもう少し詳しく知っている必要があります.
そこで,まず,A0の正体から紐解いていきましょう.

A0ってなに?

A0やA1,D0などのArduinoの端子に対応するマクロはAruino UNO R4 MinimaArduino UNO R4 WiFi で定義されているファイルが異なります.
UNO R4 MinimaではArduinoCore-renesasvariants内のMINIMAフォルダのpins_arduino.h,UNO R4 WiFiではArduinoCore-renesasのvariants内のUNOWIFIR4フォルダのpins_arduino.hにて定義されています.
しか,A0からA5の定義の仕方はどちらも同じで下記のとおりです:

#define PIN_A0   (14u)
#define PIN_A1   (15u)
#define PIN_A2   (16u)
#define PIN_A3   (17u)
#define PIN_A4   (18u)
#define PIN_A5   (19u)

static const uint8_t A0 = PIN_A0;
static const uint8_t A1 = PIN_A1;
static const uint8_t A2 = PIN_A2;
static const uint8_t A3 = PIN_A3;
static const uint8_t A4 = PIN_A4;
static const uint8_t A5 = PIN_A5;

同様に,D0からD15 (※D14はA0, D15はA1)も下記のように定義されています:

#define PIN_D0   (0u)
#define PIN_D1   (1u)
#define PIN_D2   (2u)
#define PIN_D3   (3u)
#define PIN_D4   (4u)
#define PIN_D5   (5u)
#define PIN_D6   (6u)
#define PIN_D7   (7u)
#define PIN_D8   (8u)
#define PIN_D9   (9u)
#define PIN_D10   (10u)
#define PIN_D11   (11u)
#define PIN_D12   (12u)
#define PIN_D13   (13u)
#define PIN_D14   (14u)
#define PIN_D15   (15u)

static const uint8_t D0 = PIN_D0;
static const uint8_t D1 = PIN_D1;
static const uint8_t D2 = PIN_D2;
static const uint8_t D3 = PIN_D3;
static const uint8_t D4 = PIN_D4;
static const uint8_t D5 = PIN_D5;
static const uint8_t D6 = PIN_D6;
static const uint8_t D7 = PIN_D7;
static const uint8_t D8 = PIN_D8;
static const uint8_t D9 = PIN_D9;
static const uint8_t D10 = PIN_D10;
static const uint8_t D11 = PIN_D11;
static const uint8_t D12 = PIN_D12;
static const uint8_t D13 = PIN_D13;
static const uint8_t D14 = PIN_D14;
static const uint8_t D15 = PIN_D15;

すなわち,uint8_t 型として定義されており,例えば、A0は14uという値になっています.
また,D14も14uという値になっているため,A0と同じであることがわかります.
同じようにD0は0uという値になっています.
余談ですが,D16からD19は定義されていないので注意して下さい.
これをもとに,pin_size_t 型のanalogReadで実行されるdigitalPinToAnalogPin(pinNumber)を紐解いていきましょう.

マクロdigitalPinToAnalogPinを見てみよう

analogReadでまず最初に実行されるdigitalPinToAnalogPin(pinNumber) を見てみましょう.
ArduinoCore-renesas : 特に Arduino APIのソースコードにあるArduino.hを見てみると

#define digitalPinToAnalogPin(P)    (P < A0 ? A0 + P : P)

のようにマクロで定義されていることがわかります.
引数のPpinNumberであり,すなわちanalogReadの引数です.
A0は14uであり,P < A0 であるため,pinNumberがA0以上の値であるA0〜A5は偽(False)となりPがそのまま返ってきます.
すなわち,analogReadの引数としてA0〜A5を入れたら,analogReadの1行目の

int32_t adc_idx = digitalPinToAnalogPin(pinNumber);

は,(比較演算子<と三項演算子? : で計算リソースは使われてしまいますが)

int32_t adc_idx = pinNumber;

となり,analogReadの引数pinNumberがそのまま変数adc_idxに代入されます.

では,digitalPinToAnalogPinマクロは何のためにあるのか?となってしまいます.
そこで,英語を読んで字の通りに訳すと「デジタルピンをアナログピンへ」と直訳できます.
そのため,試しに,analogRead( D0 ) のようにanalogReadだけど誤ってDのピンを指定してみましょう.
そのとき,D0 < A0 は 0 < 14 であるため真(True)になります.
その上で,A0 + P を実行するため,A0 + D0 = 14 + 0 = 14 = A0 となります.
すなわち,D0がA0になりました.

同様に,D1の場合も,D1 < A0は, 1 < 14 であるため真(True)になります.
その上で,A0 + D1 = 14 + 1 = 15 = A1となりました.
すなわち,D1がA1になりました.

同じようにD2がA2に,D3がA3に,D4がA4に,D5がA5に,それぞれミス(?)を修正してくれます.
しかし,D6を入れた場合,A0 + D6 = 14 + 6 = 20 となり適切に変換できないため注意が必要です.

以上のことから, digitalPinToAnalogPinanalogReadなのにD0ピンなどを指定してしまった場合の救済処置だと見えます.
よって適切にA0ピンなどを指定していれば,むしろ必要がない処理であることがわかります.

ADC_Containerクラスとは?

続けて,analog.cppにあるpin_size_t 型の関数analogReadの解析を進めていきます
マクロdigitalPinToAnalogPin の次は

uint16_t cfg_adc = 0;
ADC_Container *_adc = get_ADC_container_ptr(adc_idx, cfg_adc);

です.
cfg_adc = 0は初期化しているだけなので,読み飛ばしても良いでしょう.
しかし,その次のADC_Containerクラスや関数get_ADC_container_ptr複雑です.
そこで,ここでは深入りしすぎずに飛ばしながら説明していきたいと思います(一つずつ読みたい方は,是非今までの手順通り,中身のソースコードを追っていくと良いです!!).
まず,関数analogReadで利用しているADC_Containerクラスは端的に言うと,A/D変換で利用するルネサスAPI,特に,

関数R_ADC_Open
関数R_ADC_ScanCfg
関数R_ADC_ScanStart
関数R_ADC_StatusGet
関数R_ADC_Read

を使うための設定をanalog.cpp内にある関数get_ADC_container_ptrで作成し,ADC_Containerクラスのインスタンス_adcに格納しています

では,ADC_Containerクラスとは何か?から見ていきましょう.
ADC_ContainerクラスはArduinoCore-renesasanalog.h内で以下のように定義されています:

class ADC_Container {
public:
  ADC_Container(uint8_t unit, ADCCbk_f cbk, void *ctx = nullptr);
  ~ADC_Container();
  adc_instance_ctrl_t ctrl;
  adc_extended_cfg_t cfg_extend;
  adc_cfg_t cfg;
  adc_channel_cfg_t channel_cfg;
};

まず,下から4行で4つのメンバー変数

ctrl
cfg_extend
cfg
channel_cfg

を定義しています.
すなわち,analog.cpp関数analogReadのポインタ変数_adc

_adc->ctrl
_adc->cfg_extend
_adc->cfg
_adc->channel_cfg

のようにメンバ変数へアクセスできるはずです.
また,ここでは深く解析しませんが,構造体adc_instance_ctrl_tadc_extended_cfg_tadc_channel_cfg_tの3つはArduinoCore-renesasr_adc.h内に,構造体adc_cfg_tr_adc_api.h内にて定義されていますので興味がある場合,調べてみて下さい.ここまでちゃんと読んでいる方はファイル名にr_と先頭についているため,ルネサス関係の構造体だな,って推察できると思います.

しかし,analog.h内のADC_ContainerクラスにはコンストラクタADC_Containerとデストラクタ~ADC_Containerの中身はここでは定義されていません.
どちらも,analog.cppで定義されています.
どちらも詳しくは解析しませんが,このコンストラクタでA/D変換のほとんどの設定をここで行っています.(例えば,シングルスキャンモードとして ADC_MODE_SINGLE_SCAN のように設定しています.)

コンストラクタ:

ADC_Container::ADC_Container(uint8_t unit, ADCCbk_f cbk, void *ctx /*= nullptr*/) {
  cfg_extend.add_average_count    = ADC_ADD_OFF;
  cfg_extend.clearing             = ADC_CLEAR_AFTER_READ_ON;
  cfg_extend.trigger_group_b      = ADC_TRIGGER_SYNC_ELC;
  cfg_extend.double_trigger_mode  = ADC_DOUBLE_TRIGGER_DISABLED;
  cfg_extend.adc_vref_control     = ADC_VREF_CONTROL_AVCC0_AVSS0;
  cfg_extend.enable_adbuf         = 0;
  cfg_extend.window_a_irq         = FSP_INVALID_VECTOR;
  cfg_extend.window_a_ipl         = (12);
  cfg_extend.window_b_irq         = FSP_INVALID_VECTOR;
  cfg_extend.window_b_ipl         = (12);

  cfg.unit                        = unit;
  cfg.mode                        = ADC_MODE_SINGLE_SCAN;
  /* 20221109 [maidnl] FIX Requested resolution:
    Resolution of ADC is fixed to the highest possible and
    never changed, requested resolution is used to scale the values provided
    to user */
  #if 12U == BSP_FEATURE_ADC_MAX_RESOLUTION_BITS
  cfg.resolution                  = ADC_RESOLUTION_12_BIT;
  #elif 14U == BSP_FEATURE_ADC_MAX_RESOLUTION_BITS
  cfg.resolution                  = ADC_RESOLUTION_14_BIT;
  #elif 16U == BSP_FEATURE_ADC_MAX_RESOLUTION_BITS
  cfg.resolution                  = ADC_RESOLUTION_16_BIT;
  #else
  #error BSP_FEATURE_ADC_MAX_RESOLUTION_BITS is set to an unandled version

  /* should never happen... but in any case 12 is the resolution supported by
    both ADC (R7FA4M1AB and R7FA6M5BH) */

  cfg.resolution                  = ADC_RESOLUTION_12_BIT;
  #endif

  cfg.alignment                   = (adc_alignment_t) ADC_ALIGNMENT_RIGHT;
  cfg.trigger                     = ADC_TRIGGER_SOFTWARE;
  cfg.p_callback                  = cbk;
  cfg.p_context                   = ctx;
  cfg.p_extend                    = &cfg_extend;
  cfg.scan_end_irq                = FSP_INVALID_VECTOR;
  cfg.scan_end_ipl                = (12);
  cfg.scan_end_b_irq              = FSP_INVALID_VECTOR;
  cfg.scan_end_b_ipl              = (12);  

  channel_cfg.sample_hold_states  = 24;
  channel_cfg.scan_mask           = 0;         
  channel_cfg.scan_mask_group_b   = 0;
  channel_cfg.add_mask            = 0;
  channel_cfg.p_window_cfg        = nullptr;
  channel_cfg.priority_group_a    = ADC_GROUP_A_PRIORITY_OFF;
  channel_cfg.sample_hold_mask    = 0;
};

デストラクタ:
ADC_Container::~ADC_Container() {
  if(channel_cfg.p_window_cfg != nullptr) {
    delete channel_cfg.p_window_cfg;
  }
}

コンストラクタは,長く,一見複雑そうに見えますが,ほとんど,事前に定義した設定用の列挙型の値を構造体のメンバー変数に代入しているだけです.
その上で,analog.cpp 内で

static ADC_Container adc(0,ADC_irq_cbk);
static ADC_Container adc1(1,ADC_irq_cbk);

とコンストラクタを呼び出してADC_Containerクラスのインスタンスadcadc1を作成しています.
なお,詳細は省きますが,ADC_irq_cbkは割り込み(Interrupt Request)時に発動するコールバック関数であり,コールバック関数の中身もanalog.cpp定義されています.

この節で伝えたいポイントは,analog.cppにあるpin_size_t 型の関数analogReadの

ADC_Container *_adc = get_ADC_container_ptr(adc_idx, cfg_adc);

で作成している_adc上で作成したADC_Containerクラスのインスタンスadcadc1どちらか一方のアドレスであるという点です.
そこで,次は,関数get_ADC_container_ptrの中身を読んで,戻り値がadcadc1のどちらか一方のアドレスになっていることを確認してみましょう.

関数get_ADC_container_ptrとは?

引き続きanalog.cppにあるpin_size_t 型の関数analogRead
ADC_Container *_adc = get_ADC_container_ptr(adc_idx, cfg_adc);
を読んでいきましょう.
この関数get_ADC_container_ptrの戻り値が代入される_adcADC_Containerクラスのインスタンスadcadc1のどちらか一方のアドレスになっていることを前節で紹介しました.
実際にanalog.cpp関数get_ADC_container_ptrのソースコードを見てみましょう:

/* ---------------------------------------------------------- */
static ADC_Container *get_ADC_container_ptr(int32_t pin, uint16_t &cfg) {
/* ---------------------------------------------------------- */
  ADC_Container *rv = nullptr;
  auto cfg_adc = getPinCfgs(pin, PIN_CFG_REQ_ADC);
  if(cfg_adc[0] > 0 ) {
    if(IS_ADC1(cfg_adc[0])) {
      rv = &adc1;
    }
    else {
      rv = &adc;
    }
  }
  cfg = cfg_adc[0];
  return rv;
}

まず,関数の宣言部分の2つ目の引数を見てみるとuint16_t &cfgとなっています.
すなわち,2番目の引数として渡された変数cfgconstがない参照渡しであるため,上書きされます.

続いて,コードの全体を見渡して戻り値を見てみるとreturn rv;であることがわかります.
すなわち,上からrvについてみていけばよいですね.
まず,rvADC_Container *rv = nullptr;でヌルポインタに初期化されていることがわかります.
その上,関数getPinCfgsの戻り値cfg_adcを元にif文でrv = &adc1;あるいはrv = &adc;rvadc1あるいはadcのアドレスを代入していることがわかります.
よって,関数analogReadのポインタ変数_adcにはadcadc1のアドレスが入っていることがわかりました.
しかし,このままでは関数get_ADC_container_ptrは読み解けません.

では,関数getPinCfgsやその戻り値cfg_adc,また,上書きしているcfg = cfg_adc[0];は何をしているのでしょうか?
次節は,関数getPinCfgsの中身を見てcfgが何か,紐解いていきましょう.
その上で,次々節でもう一度,関数get_ADC_container_ptrに戻って来ましょう.

関数getPinCfgsってなに?

本節では,analog.cpp関数get_ADC_container_ptrで使用されている

auto cfg_adc = getPinCfgs(pin, PIN_CFG_REQ_ADC);

の関数getPinCfgsを紐解いていきましょう.

まず,第1引数pinanalogRead(pin)pinが(ほぼ)そのまま引数として引き継がれており,A0ってなに?で紹介したA0などのピン番号が格納されています.
第2引数PIN_CFG_REQ_ADCや戻り値の型(autoの実態)は関数getPinCfgsのソースコードを見ればすぐにわかります.
そのために,関数getPinCfgsを見てみましょう.
関数getPinCfgsのソースコードはルネサス社製のマイコンのArduino APIvariant_helper.cppにあります:

 std::array<uint16_t, 3> getPinCfgs(const pin_size_t pin, PinCfgReq_t req) {
  std::array<uint16_t, 3> ret = {0 , 0, 0};
  if (pin > PINS_COUNT) {
    return ret;
  }
  uint8_t cfg_idx = 0;
  const uint16_t* cfg = g_pin_cfg[pin].list;
  bool thats_all = false;
  uint8_t index = 0;
  while(!thats_all) {
    if(PIN_CFG_REQ_UART_TX == req && IS_PIN_UART_TX(*(cfg + index))) {
      ret[cfg_idx++] = *(cfg + index);
    }
    else if(PIN_CFG_REQ_UART_RX == req && IS_PIN_UART_RX(*(cfg + index))) {
      ret[cfg_idx++] = *(cfg + index);
    }

   ....中略....

    else if(PIN_CFG_REQ_ADC == req && IS_PIN_ANALOG(*(cfg + index))) {
      ret[cfg_idx++] = *(cfg + index);
    }
    else if(PIN_CFG_REQ_CAN_RX == req && IS_PIN_CAN_RX(*(cfg + index))) {
      ret[cfg_idx++] = *(cfg + index);
    }

   ....中略....

    if(IS_LAST_ITEM(*(cfg + index))) {
      thats_all = true;
    }
    else {
      index++;
    }
  }
  return ret;
}

まず,autoの正体がstd::array<uint16_t, 3>uint16_t型の要素を3つ持つ静的配列であることがわかりましたね.
その上,第2引数は仮引数reqに代入され,その上,何やらif文でPIN_CFG_REQ_ADC == reqのようなチェックをしていることがわかります.
関数getPinCfgs読んで字のごとく,Pinの設定(Configures)を得る(Get)関数です.
しかし,
例えば,bsp_io_port_pin_t 型で紹介したP000端子(ピン)はArduino UNO R4 WiFiではA1端子に接続されていますが,ルネサス社のマイコンRA4M1グループでみたらP000端子(ピン)はアナログ入力以外の機能もあることがわかります.
そのために,何の機能の設定を得るのか伝えるために関数getPinCfgsの第2引数として「AD変換用の設定だ!」と伝えるためにPIN_CFG_REQ_ADCをとっています.
すなわち,関数getPinCfgsではPinの第2引数(PinCfgReq_t req)機能の設定(Configures)を得る(Get)関数といえます.

続いて,設定はどこで読み込んでいるのか,variant_helper.cpp関数getPinCfgsのソースコードを見てみると

const uint16_t* cfg = g_pin_cfg[pin].list;

であることがわかります.
では,g_pin_cfg[pin].list何かみてみましょう.

まず,構造体の配列g_pin_cfg[]が定義されているところはArduino UNO R4 MinimaAruino UNO R4 WiFiでRA4M1グループのマイコンのピンの使い方が違うためファイルが異なります:

Arduino UNO R4 Minimavariant.cpp:

extern "C" const PinMuxCfg_t g_pin_cfg[] = {
  { BSP_IO_PORT_03_PIN_01,    P301   }, /* (0) D0  -------------------------  DIGITAL  */
  { BSP_IO_PORT_03_PIN_02,    P302   }, /* (1) D1  */
  { BSP_IO_PORT_01_PIN_05,    P105   }, /* (2) D2  */
  ....中略....
  { BSP_IO_PORT_00_PIN_14,    P014   }, /* (14) A0  --------------------------  ANALOG  */
  { BSP_IO_PORT_00_PIN_00,    P000   }, /* (15) A1  */
  { BSP_IO_PORT_00_PIN_01,    P001   }, /* (16) A2  */
  { BSP_IO_PORT_00_PIN_02,    P002   }, /* (17) A3  */
  { BSP_IO_PORT_01_PIN_01,    P101   }, /* (18) A4/SDA  */
  { BSP_IO_PORT_01_PIN_00,    P100   }, /* (19) A5/SCL  */
  ....中略....
  { BSP_IO_PORT_03_PIN_00,    P300   }, /* (26) SWCLK  */
};

Aruino UNO R4 WiFivariant.cpp:

extern "C" const PinMuxCfg_t g_pin_cfg[] = {
  { BSP_IO_PORT_03_PIN_01,    P301   }, /* (0) D0  -----  DIGITAL  */
  { BSP_IO_PORT_03_PIN_02,    P302   }, /* (1) D1  */
  { BSP_IO_PORT_01_PIN_04,    P104   }, /* (2) D2  */
  ....中略....
  { BSP_IO_PORT_00_PIN_14,    P014   }, /* (14) A0  -----  ANALOG  */
  { BSP_IO_PORT_00_PIN_00,    P000   }, /* (15) A1  */
  { BSP_IO_PORT_00_PIN_01,    P001   }, /* (16) A2  */
  { BSP_IO_PORT_00_PIN_02,    P002   }, /* (17) A3  */
  { BSP_IO_PORT_01_PIN_01,    P101   }, /* (18) A4/SDA  */
  { BSP_IO_PORT_01_PIN_00,    P100   }, /* (19) A5/SCL  */
  ....中略....
  { BSP_IO_PORT_02_PIN_13,    P213   }, /* (38) D38  */
};

列挙型BSP_IO_PORT_00_PIN_14についてはbsp_io_port_pin_t型で扱ったので大丈夫ですよね!
そのため,構造体配列g_pin_cfgでわからないのは,構造体PinMuxCfg_tと値P301(※正確には配列)などですね.

まず,構造体PinMuxCfg_tvariant.hに以下のように定義されています:

typedef struct
{
  bsp_io_port_pin_t        pin;
  const uint16_t          *list;
} PinMuxCfg_t ;

すなわち,例えば,構造体配列g_pin_cfgの14番目の要素g_pin_cfg[14]はメンバ変数として,bsp_io_port_pin_t型pinuint16_t型のポインタlistを持っていることがわかります.
具体的には,構造体配列g_pin_cfgの宣言からg_pin_cfg[15].pin = BSP_IO_PORT_00_PIN_00であり,g_pin_cfg[15].list = P000になります.

では,uint16_t*型のP000などはどこで宣言されているのでしょうか?
Arduino UNO R4 MinimaではMINIMAフォルダ内のpinmux.inc, また,Aruino UNO R4 WiFiではUNOWIFIR4フォルダのpinmux.incにあります.
ピンの接続の関係からArduino UNO R4のMinimaとWiFiではアナログ入力用の端子A0~A5は,ルネサス社製RA4M1グループのR7FA4M1AB3CFMマイコンのピンP014 (A0), P000 (A1), P001(A2),P002 (A3), P101 (A4), P100 (A5)に接続されていることが読み取れます.
そこで,P014 (A0), P000 (A1), P001(A2),P002 (A3), P101 (A4), P100 (A5)に対応するところをpinmux.incから抜粋すると下記のようになります:

const uint16_t P014[] = {
PIN_ANALOG|CHANNEL_9|LAST_ITEM_GUARD
};
const uint16_t P000[] = {
PIN_ANALOG|CHANNEL_0,
PIN_INTERRUPT|CHANNEL_6|LAST_ITEM_GUARD
};
const uint16_t P001[] = {
PIN_ANALOG|CHANNEL_1,
PIN_INTERRUPT|CHANNEL_7|LAST_ITEM_GUAR
};
const uint16_t P002[] = {
PIN_ANALOG|CHANNEL_2,
PIN_INTERRUPT|CHANNEL_2|LAST_ITEM_GUARD
};
const uint16_t P101[] = {
PIN_ANALOG|CHANNEL_21,
PIN_PWM|CHANNEL_5|PWM_CHANNEL_A|GPT_ODD_CFG,
PIN_SDA|CHANNEL_1,
PIN_INTERRUPT|CHANNEL_1,
SCI_CHANNEL|PIN_TX_MOSI_SDA|CHANNEL_0|SCI_EVEN_CFG,
SCI_CHANNEL|PIN_CTS_RTS_SS|CHANNEL_1|SCI_ODD_CFG,
PIN_MOSI|CHANNEL_0|LAST_ITEM_GUARD
};
const uint16_t P100[] = {
PIN_ANALOG|CHANNEL_22,
PIN_PWM|CHANNEL_5|PWM_CHANNEL_B|GPT_ODD_CFG,
PIN_SCL|CHANNEL_1,
PIN_INTERRUPT|CHANNEL_2,
SCI_CHANNEL|PIN_RX_MISO_SCL|CHANNEL_0|SCI_EVEN_CFG,
SCI_CHANNEL|PIN_SCK|CHANNEL_1|SCI_ODD_CFG,
PIN_MISO|CHANNEL_0|LAST_ITEM_GUARD
};

このコードを読むとP014を見てみると要素が1つの配列であることがわかります.
その上で,P014[0]にはPIN_ANALOG|CHANNEL_9|LAST_ITEM_GUARDが格納されています.
では,PIN_ANALOGなどはなんでしょうか?
これらは,variant.hで宣言されているマクロです(抜粋):

#define PIN_USE_POS                (1)
#define PIN_ANALOG                 (10 << PIN_USE_POS)
#define PIN_USE_MASK               (0x3E)  /* 0000 0000 0011 1110 */
#define IS_PIN_ANALOG(x)           ((x & PIN_USE_MASK) ==  PIN_ANALOG)
#define CHANNEL_POS                (6)
#define CHANNEL_MASK               (0x7C0)  /* 0000 0111 1100 0000 */
#define CHANNEL_0                  (0 << CHANNEL_POS)
#define CHANNEL_1                  (1 << CHANNEL_POS)

#define CHANNEL_9                  (9 << CHANNEL_POS)
#define LAST_ITEM_MASK             (0x8000)  /* 1000 0000 0000 0000 */
#define GET_CHANNEL(x)             ((x & CHANNEL_MASK) >> CHANNEL_POS)
#define LAST_ITEM_GUARD            (1 << 15)
#define IS_LAST_ITEM(x)            ((x & LAST_ITEM_MASK) ==  LAST_ITEM_GUARD)

P014uint16_tであることから16bitです.
この16bitを使ってフラグを管理しています.
例えば,16bitのうち,一番左がLAST_ITEM_GUARDで(1 << 15)となっています.
すなわち,LAST_ITEM_GUARDのフラグが立っていたら

1000 0000 0000 0000

のようになります.
また,端子を何の用途で使用するか,判別するための前処理用のマスクPIN_USE_MASK(0x3E)

0000 0000 0011 1110

となります.
すなわち,用途を管理するためのフラグは,右から2bit~6bitを利用しています.
そのために,P014[0]にはPIN_ANALOG|CHANNEL_9|LAST_ITEM_GUARD

PIN_ANALOG: 0000 0000 0001 0100
CHANNEL_9: 0000 0010 0100 0000
LAST_ITEM_GUARD: 1000 0000 0000 0000

から

1000 0010 0101 0100

となります.
余談ですが,端子P103はArduino UNO R4 MinimaではD10に,Arduino UNO R4 WiFiではD4に接続されていますが,

const uint16_t P103[] = {
PIN_ANALOG|CHANNEL_19,
PIN_CAN_TX|CHANNEL_0,
PIN_PWM|CHANNEL_2|PWM_CHANNEL_A|GPT_ODD_CFG,
SCI_CHANNEL|PIN_CTS_RTS_SS|CHANNEL_0|SCI_EVEN_CFG|LAST_ITEM_GUARD
};

のようにPIN_ANALOGの用途もあります.
端子P103などを利用してもA/D変換ができるかも!っと考えて下さると良いです!

話を戻して,variant_helper.cpp関数getPinCfgsのソースコード

const uint16_t* cfg = g_pin_cfg[pin].list;

の意味はもうわかりますよね!
端子A0とした場合,cfg = g_pin_cfg[14].listは端子P014が指定され

cfg[0] = PIN_ANALOG|CHANNEL_9|LAST_ITEM_GUARD // 1000 0010 0101 0100

となります.
同様に,端子A1とした場合,cfg = g_pin_cfg[15].listは端子P000が指定され

cfg[0] = PIN_ANALOG|CHANNEL_0 // 0000 0000 0001 0100
cfg[1] = PIN_INTERRUPT|CHANNEL_6|LAST_ITEM_GUARD // 1000 0001 1000 0010

となります.

これで,やっとこの節の主題だった関数getPinCfgsの全容を読み解くことができます.
関数getPinCfgsの簡易版を書くとなります:

const uint16_t* cfg = g_pin_cfg[pin].list;
  bool thats_all = false;
  uint8_t index = 0;
  while(!thats_all) {
    if( 

  ....中略....

    else if(PIN_CFG_REQ_ADC == req && IS_PIN_ANALOG(*(cfg + index))) {
      ret[cfg_idx++] = *(cfg + index);
    }
     ....中略....

    if(IS_LAST_ITEM(*(cfg + index))) {
      thats_all = true;
    }
    else {
      index++;
    }
  }
  return ret;

その上,

else if(PIN_CFG_REQ_ADC == req && IS_PIN_ANALOG(*(cfg + index)))

の解説に戻りましょう.
PIN_CFG_REQ_ADC == req関数getPinCfgsの第2引数で機能を指定しているところまでは,本節の最初の方で説明しました
続いて,IS_PIN_ANALOG(*(cfg + index))です.
*(cfg + index)cfg[index]と読み替えると,最初のループではIS_PIN_ANALOG(cfg[0]) になります.
例えば,端子A1とした場合cfg[0] = PIN_ANALOG|CHANNEL_0  // 0000 0000 0001 0100でしたね.
さらに,上でも記載したようにIS_PIN_ANALOGマクロは

#define IS_PIN_ANALOG(x)            ((x & PIN_USE_MASK) ==  PIN_ANALOG)

であるため,IS_PIN_ANALOG(*(cfg + index)))

((0000 0000 0001 0100 & PIN_USE_MASK) ==  PIN_ANALOG)

となります.
PIN_USE_MASKマクロやPIN_ANALOGマクロも置き換える

((0000 0000 0001 0100 & 0000 0000 0011 1110) ==  0000 0000 0001 0100)

となります.
すなわち,cfg[index] (正確には*(cfg + index))にPIN_ANALOG(0000 0000 0001 0100)が指定されているか?というのを判別するためのチェックになります.
その結果,PIN_CFG_REQ_ADC == req && IS_PIN_ANALOG(*(cfg + index))trueであれば,戻り値となる配列ret

ret[cfg_idx++] = *(cfg + index);

を代入します.
そのため,例えば,端子A1とした場合の最初のループ(index = 0)では

ret[0] = 0000 0000 0001 0100 /* PIN_ANALOG|CHANNEL_0 */

が代入されます.

代入後も最初のループ(index = 0)は続きます.
続いては,ループの終了条件です.
ループの終了条件のフラグは下記で制御されています:

if(IS_LAST_ITEM(*(cfg + index))) {
    thats_all = true;
}

ここで,IS_LAST_ITEMマクロは

#define IS_LAST_ITEM(x)             ((x & LAST_ITEM_MASK) ==  LAST_ITEM_GUARD)

でしたので,cfg[index]が指定している端子の最後の機能なのか,LAST_ITEM_GUARDのフラグを確認して判別しています.
例えば,端子A1とした場合cfg[0] = PIN_ANALOG|CHANNEL_0  /* 0000 0000 0001 0100 */のため,LAST_ITEM_GUARDのフラグである一番左のビットは0になっています.
そのため,端子A1とした場合最初のループではthats_all = true;は実行されず,index++;が実行されてループが続きます.

以降,2週目のループでも最初のif文でユーザが必要としている端子P000などの機能を取り出して,配列retに入れ,終了判定を行います.
これが関数getPinCfgsの全容になります.
なぜ,PIN_ANALOGを1つ見つけた段階で,ループを停め,returnをしないか?というと,マイコンの種類によっては複数のA/D変換器(ユニット)を持っているかもしれないからです.
しかし,ルネサス社製RA4M1グループのマイコンは「Renesas RA4M1グループ ユーザーズマニュアル ハードウェア編」のp. 1288の表 35.1を見てもわかる通り,A/D変換器の1ユニットしかありません.
そのために,Arduino UNO R4 MinimaArduino UNO R4 WiFiのA/D変換器を利用する場合には冗長な処理になってしまいます.

ループが終わったらreturn ret;だけですので,配列retの要素にはpinmux.inc内の必要な端子の必要な機能に関係するbitを格納していることがわかりました.

それでは,本命の関数get_ADC_container_ptrを読み進めていきましょう!

(続)関数get_ADC_container_ptrとは?

前々節に引き続き関数get_ADC_container_ptrの中身の解析を進めていきましょう.
関数get_ADC_container_ptrの中身を再掲すると

static ADC_Container *get_ADC_container_ptr(int32_t pin, uint16_t &cfg) {
  ADC_Container *rv = nullptr;
  auto cfg_adc = getPinCfgs(pin, PIN_CFG_REQ_ADC);
  if(cfg_adc[0] > 0 ) {
    if(IS_ADC1(cfg_adc[0])) {
      rv = &adc1;
    }
    else {
      rv = &adc;
    }
  }
  cfg = cfg_adc[0];
  return rv;
}

でした.
関数getPinCfgsは「関数getPinCfgsってなに?で,ADC_Containerクラスのインスタンス adcadc1は「ADC_Containerクラスとは?」で紹介しましたね.
特に,配列cfg_adcには例えば端子A1とした場合cfg_adc[0] = PIN_ANALOG|CHANNEL_0  /* 0000 0000 0001 0100 */のような値が入っていましたね.
そのために,if(cfg_adc[0] > 0 )は,ちゃんと値を取得できているか,チェックを行っているだけです.

それに対して,IS_ADC1(cfg_adc[0])は初出です.
IS_ADC1PIN_ANALOGマクロなどと同じようにvariant.hにて定義されています:

#define ADD_CONFIG_POS      (11)
#define ADC_0               (0 << ADD_CONFIG_POS)
#define IS_ADC0(x)          ((x & ADD_CONFIG_MASK) == ADC_0)
#define ADC_1               (1 << ADD_CONFIG_POS)
#define IS_ADC1(x)          ((x & ADD_CONFIG_MASK) == ADC_1)

マクロADC_0やマクロADC_1は,マイコンに複数のA/D変換器がある際に,例えば端子P000のフラグとしてPIN_ANALOG|CHANNEL_0|ADC_1のように,どのA/D変換器を利用するか指定するためのものです.
しかし,ルネサス社製RA4M1グループのマイコンは「Renesas RA4M1グループ ユーザーズマニュアル ハードウェア編」のp. 1288の表 35.1を見てもわかる通り,A/D変換器の1ユニットしかありません.
そのために,端子の設定を行っているArduino UNO R4 Minimapinmux.incAruino UNO R4 WiFipinmux.incではマクロADC_0やマクロADC_1が使われていません.
そのために,デフォルト値である0(すなわち,マクロADC_0)を利用した時の設定になります.
そので,IS_ADC1ADC_1のフラグが立っているか?をチェックするためのマクロです.
すなわち,Arduino UNO R4 MinimaあるいはAruino UNO R4 WiFiを利用する限りは,必ず,

rv = &adc;

が実行されるため,ADC_Containerクラスのインスタンス adcしか利用していないことがわかります.

また,関数get_ADC_container_ptr第2引数cfgは,constがない参照渡しでしたため,上書きできます.
第2引数cfgをif文後の

cfg = cfg_adc[0];

で行っています.
すなわち,例えば端子A1とした場合cfg = PIN_ANALOG|CHANNEL_0  /* 0000 0000 0001 0100 */のように端子の設定をcfgに代入していることがわかります.

以上をまとめると,関数get_ADC_container_ptrは,戻り値はADC_Containerクラスのインスタンス adcであり,第2引数として指定された端子のpinmux.incで定義されたA/D変換に関する設定を返している関数といえます.

それでは,続けてpin_size_t 型の関数analogReadを読み進めていきましょう.
関数analogReadを見てみると関数get_ADC_container_ptrの次は関数pinPeripheralです.
そこで,次は関数pinPeripheralを見ていきましょう.

関数pinPeripheralってなに?

関数analogReadの次の解析対象は

pinPeripheral(digitalPinToBspPin(adc_idx),(uint32_t) IOPORT_CFG_ANALOG_ENABLE);

です.

まず,マクロdigitalPinToBspPinを見てみましょう.
マクロdigitalPinToBspPinArduino.hで定義されています:

#define digitalPinToBspPin(P)       (g_pin_cfg[P].pin)

これを見るとdigitalPinToBspPin(adc_idx)g_pin_cfg[adc_idx].pinを意味します.
構造体PinMuxCfg_tの構造体配列g_pin_cfgは「関数getPinCfgsってなに?」で扱いましたね.
関数getPinCfgsってなに?」を読み返すと,例えば端子A0 (P014)の場合,g_pin_cfg[A0].pinBSP_IO_PORT_00_PIN_14になることがわかります.
なお,BSP_IO_PORT_00_PIN_14bsp_io_port_pin_t型で「pin_size_t型とbsp_io_port_pin_t型」で扱いましたね.

続いて,IOPORT_CFG_ANALOG_ENABLEは列挙型ioport_cfg_options_tの値としてArduino UNO R4 Minimaではr_ioport_api.hArduino UNO R4 WiFiではr_ioport_api.hにて以下のように宣言されています:

/** Options to configure pin functions  */
typedef enum e_ioport_cfg_options
{
    IOPORT_CFG_PORT_DIRECTION_INPUT  = 0x00000000, ///< Sets the pin direction to input (default)
    ....中略....
    IOPORT_CFG_ANALOG_ENABLE         = 0x00008000, ///< Enables pin to operate as an analog pin
    ....中略....
} ioport_cfg_options_t;

ファイルr_ioport_api.hからわかるようにr_と書かれているため列挙型ioport_cfg_options_tルネサスAPI(RA Flexible Software Package)で定められている列挙型です.

それではいよいよ関数pinPeripheralを見てみましょう.
関数pinPeripheralpinDefinitions.cppに定義されています:

void pinPeripheral(bsp_io_port_pin_t bspPin, uint32_t bspPeripheral) {
    R_IOPORT_PinCfg(&g_ioport_ctrl, bspPin, bspPeripheral);
}

pinDefinitions.cppには引数がuint32_t型の関数pinPeripheralも定義されていますが,引数がbsp_io_port_pin_t型の関数pinPeripheralが使われるため,bsp_io_port_pin_t型のみ掲載しています.

関数pinPeripheralでは

R_IOPORT_PinCfg(&g_ioport_ctrl, bspPin, bspPeripheral);

のみ実行されていることがわかります.
勘が良い方はすぐにわかりますが,関数R_IOPORT_PinCfgルネサスAPI(RA Flexible Software Package)です.
ルネサスAPI(RA Flexible Software Package)関数R_IOPORT_PinCfgのドキュメントを読む限り,端子の構成を設定する関数であることがわかります.
すなわち,端子bspPinbspPeripheralで設定する,という意味です.
例えば,A0(BSP_IO_PORT_00_PIN_14)をIOPORT_CFG_ANALOG_ENABLE(アナログ入力のConfigure)で設定する,という意味に読み取れます.
ただし,ドキュメントにも書かれているように,関数R_IOPORT_PinCfgは他のすべての端子の設定を変更しないで,ピンのある設定のみを変更することはできません。
関数R_IOPORT_PinCfgは端子の構成をまるっきり新しい構成に変更する際に利用します.
逆に言うと,連続してanalogRead(A0);のようにA0端子のみのアナログ入力を行うような場合には,最初の1回のみ実行すればよいようにみえます.

なお,g_ioport_ctrlは特に情報がなく,ブラックボックスになっているため,解析はできません.
すなわち,関数pinPeripheralは指定された端子をアナログ入力に設定している関数であることがわかりました.

これでいよいよ関数analogReadも最後の行で使用される関数adcConvertまで来ました.
それでは,いよいよ関数adcConvertの解析に取り掛かりましょう.

関数adcConvertってなに?

いよいよ最後の関数adcConvertを見ていきましょう.
関数adcConvertanalog.cppにて下記のように定義されています:

/* ----------------------------------------------------- */
static int adcConvert(ADC_Container *_adc,uint16_t cfg_adc) {
/* ----------------------------------------------------- */ 
  if(_adc != nullptr) {
    _adc->cfg.mode = ADC_MODE_SINGLE_SCAN;
    // Enable scan only for the current pin
    // USE | (OR) to keep previous configuration (like for IRQ conversion)
    _adc->channel_cfg.scan_mask |= (1 << GET_CHANNEL(cfg_adc));

    R_ADC_Open(&(_adc->ctrl), &(_adc->cfg));
    R_ADC_ScanCfg(&(_adc->ctrl), &(_adc->channel_cfg));
    R_ADC_ScanStart(&(_adc->ctrl));

    adc_status_t status;
    status.state = ADC_STATE_SCAN_IN_PROGRESS;
    while (ADC_STATE_SCAN_IN_PROGRESS == status.state)
    {
      R_ADC_StatusGet(&(_adc->ctrl), &status);
    }

    uint16_t result;
    R_ADC_Read(&(_adc->ctrl), (adc_channel_t)GET_CHANNEL(cfg_adc), &result);

    result = map(result, 0, (1 << _privateGetHwAnalogResolution()), 0, (1 << _analogRequestedReadResolution));

    return (int)result;
  }
  else {
    return -1;
  }
}

引数_adcは「ADC_Containerクラスとは?で,引数cfg_adcは「関数get_ADC_container_ptrとは?」で作成した値ですね.
最初のif文は引数_adcに値が入っているか,確認するためのものなので良いでしょう.

続いて,_adc->cfg.mode = ADC_MODE_SINGLE_SCAN;を実行しています.
しかし,ADC_Containerクラスとは?」で扱ったADC_Containerクラスのコンストラクタコードをよく見ると,cfg.mode = ADC_MODE_SINGLE_SCAN;としているために,Arduino Uno R4でマイコンRA4M1シリーズのA/D変換をシングルスキャンモード利用する限りは不要なコードに見えます.

続いて,_adc->channel_cfg.scan_mask |= (1 << GET_CHANNEL(cfg_adc));関数getPinCfgsってなに?を振り返ると第2引数cfg_adcからチャネル(例えばP014だとCHANNEL_9)の情報を取り出して,チャネルに対応するフラグを_adc->channel_cfg.scan_maskに代入しています.

ここまでは,まだ設定の調整でした.
ここからいよいよ,A/D変換の処理になります.
A/D変換の処理には,下記の5つのルネサスAPI(RA Flexible Software Package)が利用されています:

まず,今まで構造体_adcに入れていたA/D変換器の設定を関数R_ADC_Openを使ってA/DコントロールレジスタADCSR(参照:ルネサス社製のマイコンRA4M1シリーズの「Renesas RA4M1グループ ユーザーズマニュアル ハードウェア編」のp. 129835.2.3. A/Dコントロールレジスタ(ADCSR))に書き込んでいるのだと推測できます.
また,同様に関数R_ADC_ScanCfgを使ってA/Dチャネル選択レジスタADANSA0など(参照:ルネサス社製のマイコンRA4M1シリーズの「Renesas RA4M1グループ ユーザーズマニュアル ハードウェア編」のp. 1302の35.2.4. A/Dチャネル選択レジスタA0 (ADANSA0))に書き込んでいるのだと推測できます.