STM32WLの内部Sub-GHz無線インターフェース信号のGPIOピンへのマッピング

はじめに

STM32WL line of MCUの他の周辺回路とは異なり、ユーザーは、メモリマップされたレジスタのセットを通じてSub-GHz無線部と通信するわけではありません。そうではなく、図1の赤枠部分に示すように、専用の内部SPIインターフェース(SUBGHZSPIと呼ばれる)が、RFサブシステムとの通信に使用されます。したがって、無線システムのファームウェアは、外部トランシーバのファームウェアとほぼ同じように設計することができます。同様に、これにより標準的なMCUの既存のアプリケーションと外部トランシーバの移行も、はるかに簡単になります。しかし、ファームウェアの開発中に問題が発生し、システムをデバッグする必要が生じた場合、外部ツール(ロジックアナライザやオシロスコープなど)を使用して、これらの内部通信信号を検査することができるのか、どのように検査するのか疑問に思うかもしれません。その答えは、数本のGPIOピンを追加設定するだけで、簡単に実現できます。

図1: STM32WL55/54xxのブロック図におけるSub-GHz無線システム
       専用の内部SPIインターフェース。DS13293の図1から転載。

GPIOピンの構成

デバッグのために外部に出すことができる内部信号は、いったいどのようなものなのでしょうか。図2は、Sub-GHz無線のブロック図の右側に、最も重要な信号を示したものです。それらはSUBGHZSPI(NSS、SCK、MISO、MOSI)信号、BUSY信号、割り込み(IRQ0、IRQ1、IRQ2)信号、そしてHSERDY信号です。図にはありませんが、NRESET信号、SMPSRDY信号、LDORDY信号も使用できます。これらの信号の機能に関する詳細な文書については、Reference Manualを参照してください。

図2: Sub-GHz無線機のブロック図。RM0453のマニュアルの図9。

以下の表 1 に、STM32WL55JCデバイスの内部無線インターフェースで使用可能なすべての信号、対応するGPIO ピン、およびそれらをアクティブにするために必要なオルタネート機能の値を示します。このデバイスは、Nucleo-WL55JC評価ボード(執筆時点で利用可能な唯一のSTM32WL Nucleoボード)で使用されているため、デモ用に選択されました。これらの構成については、このデバイスのデータシートの表20を参照してください。もちろん、他のデバイスを使用する場合は、必ずそのデバイスのデータシートを参照してください。

表1: STM32WLのSub-GHz無線周辺部の信号とそれに対応するGPIOピンと、それらを外部化するために使用されるオルタネート機能の値。

信号 ピン オルタネート機能
DEBUG_SUBGHZSPI_NSSOUT PA4 AF13
DEBUG_SUBGHZSPI_SCKOUT PA5 AF13
DEBUG_SUBGHZSPI_MISOOUT PA6 AF13
DEBUG_SUBGHZSPI_MOSIOUT PA7 AF13
DEBUG_RF_HSE32RDY PA10 AF13
DEBUG_RF_NRESET PA11 AF13
DEBUG_RF_SMPSRDY PB2 AF13
DEBUG_RF_LDORDY PB4 AF13
RF_BUSY PA12 AF6
RF_IRQ0 PB3 AF6
RF_IRQ1 PB5 AF6
RF_IRQ2 PB8 AF6

注:PB3ピンには、オルタネート機能の値AF13で利用できるDEBUG_RF_DTB1という別のDEBUG_RF信号が存在します。この信号は、2つの理由から表1に含まれていません。1)RF_IRQ0と競合すること、2)(私の知る限り)文書化されていないこと

デバッグ用GPIOピンは、他のGPIOと同様に設定することができます。最もシンプルで一般的な設定方法は、リスト1に示すように、HALライブラリを使用する方法です。このコードは参考として使用することを意図しているため、表1のすべてのピンが設定されています。これらのピンのサブセットが他の目的に使用されているアプリケーションでは、競合が発生するため、このようなことはできないかもしれません。どのピンを設定し、コードのどの時点で設定するかは、アプリケーション特有のデバッグの必要性に完全に依存します。

リスト1: HALライブラリを使用して、内部のSub-GHz無線インターフェース信号の値を反映するためにGPIOピンを設定する。

GPIO_InitTypeDef GPIO_InitStruct = {0};

// Enable GPIO Clocks
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();

// DEBUG_SUBGHZSPI_{NSSOUT, SCKOUT, MSIOOUT, MOSIOUT} pins
GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF13_DEBUG_SUBGHZSPI;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// DEBUG_RF_{HSE32RDY, NRESET} pins
GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_11;
GPIO_InitStruct.Alternate = GPIO_AF13_DEBUG_RF;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// DEBUG_RF_{SMPSRDY, LDORDY} pins
GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_4;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

// RF_BUSY pin
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Alternate = GPIO_AF6_RF_BUSY;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// RF_{IRQ0, IRQ1, IRQ2} pins
GPIO_InitStruct.Pin = GPIO_PIN_3 | GPIO_PIN_5 | GPIO_PIN_8;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

HALライブラリが利用できない場合(あるいは単に利用したくない場合)、以下のリスト2のコードはリスト1のコードと機能的に同等です。違いは、ハードウェアの抽象化に頼るのではなく、GPIOレジスタを直接設定する点です。

リスト2: レジスタを直接変更することで、内部のSub-GHz無線インターフェース信号の値を反映するようにGPIOピンを設定する。

// Enable GPIO Clocks
SET_BIT(RCC->AHB2ENR, (RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOBEN)); // Enable IO port A and B clocks
__asm("nop"); // After the enable bit is set, there is a two clock cycles delay
__asm("nop"); // before the clock is active in the peripheral (7.2.21 in RM0453).

// Configure GPIO
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT4); // output push-pull
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT5); // output push-pull
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT6); // output push-pull
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT7); // output push-pull
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT10); // output push-pull
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT11); // output push-pull
CLEAR_BIT(GPIOB->OTYPER, GPIO_OTYPER_OT2); // output push-pull
CLEAR_BIT(GPIOB->OTYPER, GPIO_OTYPER_OT4); // output push-pull
CLEAR_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT12); // output push-pull
CLEAR_BIT(GPIOB->OTYPER, GPIO_OTYPER_OT3); // output push-pull
CLEAR_BIT(GPIOB->OTYPER, GPIO_OTYPER_OT5); // output push-pull
CLEAR_BIT(GPIOB->OTYPER, GPIO_OTYPER_OT8); // output push-pull

MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE4_Msk, (0b10 << GPIO_MODER_MODE4_Pos)); // Alternate function mode
MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE5_Msk, (0b10 << GPIO_MODER_MODE5_Pos)); // Alternate function mode
MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE6_Msk, (0b10 << GPIO_MODER_MODE6_Pos)); // Alternate function mode
MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE7_Msk, (0b10 << GPIO_MODER_MODE7_Pos)); // Alternate function mode
MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE10_Msk, (0b10 << GPIO_MODER_MODE10_Pos)); // Alternate function mode
MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE11_Msk, (0b10 << GPIO_MODER_MODE11_Pos)); // Alternate function mode
MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODE2_Msk, (0b10 << GPIO_MODER_MODE2_Pos)); // Alternate function mode
MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODE4_Msk, (0b10 << GPIO_MODER_MODE4_Pos)); // Alternate function mode
MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE12_Msk, (0b10 << GPIO_MODER_MODE12_Pos)); // Alternate function mode
MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODE3_Msk, (0b10 << GPIO_MODER_MODE3_Pos)); // Alternate function mode
MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODE5_Msk, (0b10 << GPIO_MODER_MODE5_Pos)); // Alternate function mode
MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODE8_Msk, (0b10 << GPIO_MODER_MODE8_Pos)); // Alternate function mode

MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD4_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD5_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD6_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD7_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD10_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD11_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOB->PUPDR, GPIO_PUPDR_PUPD2_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOB->PUPDR, GPIO_PUPDR_PUPD4_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOA->PUPDR, GPIO_PUPDR_PUPD12_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOB->PUPDR, GPIO_PUPDR_PUPD3_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOB->PUPDR, GPIO_PUPDR_PUPD5_Msk, 0); // no pull-up/down
MODIFY_REG(GPIOB->PUPDR, GPIO_PUPDR_PUPD8_Msk, 0); // no pull-up/down

MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED4_Msk, (0b11 << GPIO_OSPEEDR_OSPEED4_Pos)); // high output speed
MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED5_Msk, (0b11 << GPIO_OSPEEDR_OSPEED5_Pos)); // high output speed
MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED6_Msk, (0b11 << GPIO_OSPEEDR_OSPEED6_Pos)); // high output speed
MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED7_Msk, (0b11 << GPIO_OSPEEDR_OSPEED7_Pos)); // high output speed
MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED10_Msk, (0b11 << GPIO_OSPEEDR_OSPEED10_Pos)); // high output speed
MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED11_Msk, (0b11 << GPIO_OSPEEDR_OSPEED11_Pos)); // high output speed
MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED2_Msk, (0b11 << GPIO_OSPEEDR_OSPEED2_Pos)); // high output speed
MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED4_Msk, (0b11 << GPIO_OSPEEDR_OSPEED4_Pos)); // high output speed
MODIFY_REG(GPIOA->OSPEEDR, GPIO_OSPEEDR_OSPEED12_Msk, (0b11 << GPIO_OSPEEDR_OSPEED12_Pos)); // high output speed
MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED3_Msk, (0b11 << GPIO_OSPEEDR_OSPEED3_Pos)); // high output speed
MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED5_Msk, (0b11 << GPIO_OSPEEDR_OSPEED5_Pos)); // high output speed
MODIFY_REG(GPIOB->OSPEEDR, GPIO_OSPEEDR_OSPEED8_Msk, (0b11 << GPIO_OSPEEDR_OSPEED8_Pos)); // high output speed

MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFSEL4_Msk, (0xD << GPIO_AFRL_AFSEL4_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFSEL5_Msk, (0xD << GPIO_AFRL_AFSEL5_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFSEL6_Msk, (0xD << GPIO_AFRL_AFSEL6_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOA->AFR[0], GPIO_AFRL_AFSEL7_Msk, (0xD << GPIO_AFRL_AFSEL7_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOA->AFR[1], GPIO_AFRH_AFSEL10_Msk, (0xD << GPIO_AFRH_AFSEL10_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOA->AFR[1], GPIO_AFRH_AFSEL11_Msk, (0xD << GPIO_AFRH_AFSEL11_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOB->AFR[0], GPIO_AFRL_AFSEL2_Msk, (0xD << GPIO_AFRL_AFSEL2_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOB->AFR[0], GPIO_AFRL_AFSEL4_Msk, (0xD << GPIO_AFRL_AFSEL4_Pos)); // AF13 (Debug) selected
MODIFY_REG(GPIOA->AFR[1], GPIO_AFRH_AFSEL12_Msk, (0x6 << GPIO_AFRH_AFSEL12_Pos)); // AF6 (RF) selected
MODIFY_REG(GPIOB->AFR[0], GPIO_AFRL_AFSEL3_Msk, (0x6 << GPIO_AFRL_AFSEL3_Pos)); // AF6 (RF) selected
MODIFY_REG(GPIOB->AFR[0], GPIO_AFRL_AFSEL5_Msk, (0x6 << GPIO_AFRL_AFSEL5_Pos)); // AF6 (RF) selected
MODIFY_REG(GPIOB->AFR[1], GPIO_AFRH_AFSEL8_Msk, (0x6 << GPIO_AFRH_AFSEL8_Pos)); // AF6 (RF) selected

Nucleo-WL55JCによるSub-GHz無線インターフェース信号の検査

提供されたコードを使用して内部無線インターフェース信号を外部から観察できるようにする例として、「SubGhz_Phy_PingPong」というサンプルプログラム(STM32CubeWL MCU Packageに収録)を修正、コンパイルしてNucleo-WL55JC評価ボードにロードします。その後、ロジックアナライザを使用して信号活動をキャプチャし、可視化します。

最初のステップは、GPIOの競合を確認することです。プロジェクトの STM32CubeMX デバイスコンフィギュレーションファイル(.ioc ファイル)を開き、ピンアウトビューを確認すると、表1に示すGPIOピンはすべて未使用であることがわかります。したがって、アプリケーションの機能に影響を与えることなく、すべてのピンを対応する内部信号と一致するように設定することが可能です。

次に、GPIOの設定コードをプロジェクトのソースコードに追加する必要があります。この場合、最も簡単な方法は、リスト1またはリスト2の内容をコピーして、main.c ファイルの無線初期化ルーチンの直前に貼り付けることです。つまり、SystemClock_Config() function between the /* USER CODE BEGIN SysInit *//* USER CODE END SysInit */のコメントの間にコードを貼り付けます。次に、アプリケーションをビルドして、Nucleoボードをプログラミングします。

図3: GPIOピンを適切に設定した場合のNucleo-WL55JC
                  ボード上のサブGHz無線インターフェース信号の位置。

図3は、Nucleoボードのどのヘッダピンが表1の信号ピンに対応するかを示しています。これらのピンにAnalog Discovery 2のデジタルI/Oの信号線を接続し、波形観測のアプリケーションを適切に構成して、適切な時間軸で信号値をキャプチャしました。そのようなキャプチャの1つの結果を図4に示します。この場合のSPIバスの動作は、IRQ0信号の割り込み要求の結果であるように見えます。また、無線部がスタンバイモードに入ったときのSMPSRDY信号の変化と、無線部がコマンドを処理したときのBUSY信号の変化にも注目してください。拡大すると、SPIプロトコルのデータが表示され、無線システムに送信されたコマンドとその応答を正確に見ることができます。

図4: ロジックアナライザの波形画面のスクリーンショット
               (内部無線インターフェース信号のタイミング図を示す)。




オリジナル・ソース(English)