서론
STM32WL 라인 MCU의 다른 주변 장치와는 달리, 사용자는 메모리가 매핑된 레지스터 일부를 통해 Sub-GHz 무선과 소통하지 않습니다. 대신, 그림 1의 강조된 부분과 같이 (SUBGHZSPI라고 부르는) 전용 내부 SPI 인터페이스가 RF 서브시스템과의 통신에 사용됩니다. 따라서, 무선 시스템의 펌웨어를 외부 트랜시버의 펌웨어와 거의 동일한 방식으로 설계할 수 있습니다. 마찬가지로, 표준 MCU와 외부 트랜시버에 기반한 기존 응용 프로그램을 마이그레이션하는 것도 훨씬 더 간단해집니다. 그러나, 펌웨어 개발 중 문제가 발생해서 시스템을 디버깅해야 하는 경우, 이러한 내부 통신 신호를 외부 도구(예: 로직 분석기 또는 오실로스코프)를 사용하여 검사할 수 있는지 여부와 방법에 대해 궁금해할 수 있습니다. 대답은 외부 GPIO 핀 몇 개를 추가로 구성하는 것만큼 간단합니다.
GPIO 핀 설정하기
정확하게 어떤 내부 신호를 디버깅 목적으로 외부에서 사용할 수 있을까요? 그림 2에서 가장 중요한 신호들은 Sub-GHz 무선 블록 다이어그램의 오른쪽에 있습니다. SUBGHSPI(NSS, SCK, MISO 및 MOSI) 신호, BUSY 신호, 인터럽트(IRQ0, IRQ1 및 IRQ2) 신호 및 HSERDY 신호입니다. 표시되지는 않았지만 NRESET, SMPSRDY 및 LDORDY 신호도 있습니다. 이러한 신호의 기능에 대한 자세한 자료는 참조 매뉴얼을 참고하시기 바랍니다.
아래 표 1에는 STM32WL55JC 소자의 내부 무선 인터페이스에 사용 가능한 모든 신호, 해당 GPIO 핀 그리고 이를 활성화하기 위해 필요한 대체 기능 값이 나열되어 있습니다. 게시글 작성 당시 사용 가능한 유일한 STM32WL Nucleo 기판은 Nucleo-WL55JC로, 해당 평가 기판에 사용된 소자를 예로 들어 선택하였습니다. 이 표의 내용은 해당 소자 규격서의 표 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 신호가 있습니다. 이 신호는 두 가지 이유로 표 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 무선 인터페이스 신호 검사하기
제공된 코드를 사용하여 내부 무선 인터페이스 신호를 외부에서 관찰할 수 있도록 만드는 예제로서, (STM32CubeWL MCU Package에서 이용 가능한) "SubGhz_Phy_PingPong"이라는 제목의 예제 프로젝트를 수정, 컴파일 및 Nucleo-WL55JC 기판에 로딩할 것입니다. 그런 다음, 로직 분석기를 사용하여 신호의 움직임을 캡처하고 시각화합니다.
첫 번째 단계는 GPIO의 충돌을 확인하는 것입니다. 프로젝트의 STM32CubeMX 소자 설정 파일(.ioc 파일)을 열고 Pinout 창을 확인하면 테이블 1에 설명된 모든 GPIO 핀들이 사용되고 있지 않음을 확인할 수 있습니다. 따라서, 응용 프로그램의 기능에 영향을 주지 않으면서 모든 핀들을 해당하는 내부 신호와 일치하도록 설정할 수 있습니다.
다음으로, GPIO 설정 코드를 프로젝트 소스 코드에 추가해야 합니다. 이 경우, 가장 간단한 방법은 목록 1 또는 목록 2의 내용을 복사하여 main.c
파일의 무선 초기화 루틴 직전에 붙여 넣는 것입니다. 즉, /* USER CODE BEGIN SysInit */
과 /* USER CODE END SysInit */
주석 사이 SystemClock_Config()
함수 직후에 코드를 붙여 넣습니다. 그런 다음, 응용 프로그램을 빌드하고 Nucleo 기판에 프로그래밍합니다.
그림 3은 표 1의 신호 핀에 해당하는 Nucleo 기판의 헤더 핀을 보여줍니다. 주)Analog Discovery 2 장치의 디지털 I/O 라인이 이 핀들에 연결되어 있고 적절한 시간 프레임 동안 신호 값을 캡쳐할 수 있도록 WaveForms 프로그램도 올바르게 설정되어 있습니다. 이렇게 캡쳐한 결과가 그림 4에 나와 있습니다. 이 경우 SPI 버스의 움직임은 IRQ0 라인의 인터럽트 요청에 의한 것으로 보입니다. 또한 무선 시스템이 대기 모드(standby mode)로 진입할 때 SMPSRDY 신호의 변화와 무선 시스템이 명령을 처리할 때 BUSY 신호의 변화에 주목하십시오. 확대하면 SPI 프로토콜의 인터프리터 값을 볼 수 있게 되어, 어떤 명령이 무선 시스템에 전송되었고 응답이 무엇이었는지 정확하게 알아낼 수 있습니다.
주) Analog Discovery 2는 단종되었으며 Analog Discovery 3로 대체되었습니다.
영문 원본: Mapping the Internal STM32WL Sub-GHz Radio Interface Signals to GPIO Pins