STM32WL 계열에 로우 레벨 Sub-GHz 무선 드라이버 사용하기

서론

STM32WL 계열 소자들은 LoRa(STM32WLE5/55 소자만 가능), (G)FSK, (G)MSK 그리고 BPSK 변조 방식을 지원하는 Sub-GHz 무선 주변장치를 내장하고 있습니다. 이 무선 주변장치와의 통신은 명령어를 사용하여 내부 SPI 인터페이스를 통해 이루어지며, 이는 소자의 레퍼런스 매뉴얼 섹션 5.8에 서술되어 있습니다. 이 RF 인터페이스에 대한 추상화 계층이 Sub-GHz 파이 미들웨어([STM32CubeWL MCU 패키지]에서 제공)에 정의되어 있지만, 이 미들웨어를 STM32CubeMX를 사용하여 프로젝트에 추가하기 위해서는 여러 다른 주변장치와 라이브러리 간 종속성을 갖는 고급 설정이 필요합니다. 그 결과로 프로젝트는 더 크고 더 복잡해져 더 많은 소자 메모리를 사용하고 여러 추상화 관련 비효율성이 발생합니다. 최소한의 전력 소비를 요구하는 단순한 응용 분야의 경우, RF 인터페이스 드라이버를 Sub-GHz 파이 미들웨어에서 분리하여 직접 활용하는 것이 더 나을 수 있습니다.

Sub-GHz 파이 미들웨어는 하이 레벨 계층(radio.c)과 로우 레벨 계층(radio_driver.c)으로 구성됩니다. 하이 레벨 드라이버는 RadioInit(), RadioSetTxConfig() 그리고 RadioSend()와 같은 로우 레벨 무선 기능을 추상화하는 많은 유용한 함수를 제공합니다. 이러한 함수들이 편리하기는 하지만, 불필요한 함수를 호출한다거나 시퀀서와 타이머 서버 같은 유틸리티에 대해 과도하게 의존하는 방식으로 비효율성이 발생합니다. 로우 레벨 드라이버는 단순히 레퍼런스 매뉴얼에 서술된 SUBGHZSPI 명령을 실행하며 Sub-GHz 무선 레지스터에 대한 정의를 제공합니다. 유지 보수성과 이식성 같은 일부 품질 특성의 희생을 감수하고 이 드라이버를 직접 코딩하면 프로그래머는 응용 프로그램에 대해 더 큰 제어권을 행사할 수 있습니다. 이 튜토리얼에서는 Sub-GHz 미들웨어에서 이 로우 레벨 계층을 분리하여 STM32CubeIDE 프로젝트에 직접 추가하는 방법을 보여줍니다.

요구사항

튜토리얼을 똑같이 따라하기 위해서는 다음 항목들이 필요합니다.

절차

프로젝트 생성 및 설정

첫 번째 단계는 드라이버를 추가할 수 있는 프로젝트를 만드는 것입니다. 이제 새로운 STM32 프로젝트를 처음부터 생성하고 Sub-GHz 주변장치를 활성화할 것입니다. 올바르게 설정한 기존 프로젝트가 있다면 사용할 수도 있습니다.

  1. STM32CubeIDE를 실행하고 원하는 작업 공간을 열거나 생성합니다. 새로운 프로젝트를 만들기 위해, File > New > STM32 Project를 선택하여 STM32 Project 마법사를 시작합니다.

  2. Board Selector 탭을 사용하여 NUCLEO-WL55JC1 평가 기판을 선택한 후 Next를 클릭합니다.

  1. 프로젝트에 적합한 이름(예: “radioDriverExample”)을 부여하고 Enable Multi Cpus Configuration 옵션은 선택 취소를 한 후 Finish를 클릭합니다.
  1. 주변장치를 디폴트 모드로 초기화할 지 묻는 팝업 창이 나타나면 Yes를 클릭합니다.

  2. 마법사가 “.ioc” 확장자를 가진 장치 설정 파일을 생성한 후 해당 파일이 STM32CubeIDE에서 열릴 것입니다. Pinout & Configuration 탭에서 Connectivity 카테고리 아래에 있는 SUBGHZ 주변장치를 선택합니다. Activated 옆의 체크 박스를 선택합니다.

  1. SUBGHZ의 Configuration 섹션에서 Parameter Settings 탭 아래의 Baudrate Prescaler Value를 "4"로 변경합니다.

NVIC Settings 탭에서, SUBGHZ Radio InterruptEnable 체크 박스를 선택합니다.

  1. Clock Configuration 탭으로 바꾸고 MSI RC 값을 "48000"으로 변경합니다.
  1. 마지막으로 Project Manager 탭으로 바꾸고 Code Generator 옵션을 엽니다. Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral 옆의 체크 박스를 선택합니다.
  1. 수정된 .ioc 파일을 저장하고 "Do you want to generate code?"라고 물으면 Yes를 클릭합니다. C/C++ perspective를 열 것인지 묻는 경우 다시 한번 Yes를 클릭합니다.

Sub-GHz 무선 드라이버 추가

  1. 사전 단계로 ST 웹사이트에서 STM32CubeWL MCU Package를 다운로드하고 원하는 위치에 압축을 해제합니다.

BSP 드라이버 추가

Sub-GHz 미들웨어는 하드웨어에 종속되지 않으므로, 드라이버가 RF 스위치를 제어할 수 있도록 하기 위해서는 보드 지원 패키지(Board Support Package, BSP)가 필요합니다.

  1. Project Explorer에서, Drivers 디렉토리를 마우스 오른쪽 클릭 후 **Import…**를 선택합니다. 마법사가 나타나면, 아래와 같이 General 카테고리에서 File System을 선택한 후, Next를 클릭합니다.
  1. From directory 필드를 STM32CubeWL MCU 패키지의 Drivers 디렉토리 위치로 선택합니다 (즉, <압축 해제 위치>/STM32Cube_FW_WL_V1.1.0/Drivers). 파일 탐색기 창에서 Drivers/BSP/STM32WLxx_Nucleo 디렉토리에 있는 .h와 .c 파일을 모두 선택한 후, Finish를 클릭합니다.
  1. Project Explorer에서 stm32wlxx_nucleo_conf_template.h라는 이름의 임포트한 파일을 찾아 stm32wlxx_nucleo_conf.h로 변경합니다. 이제 프로젝트 구조는 아래처럼 보여야 합니다.

stm32cubeide_projectExplorerBSP

  1. 컴파일러가 이 새로운 헤더 파일을 찾기 위해서는, 파일이 포함된 디렉토리가 include 디렉토리 목록에 추가되어야 합니다. “STM32WLxx_Nucleo” 디렉토리를 마우스 오른쪽 클릭 후 Add/remove include directory를 선택합니다. 어떤 설정을 변경할 지 묻는 창이 뜰 것입니다. DebugRelease 옵션이 모두 선택되었는지 확인하고 OK를 클릭합니다.

필수 유틸리티 추가

현 상태에서, 로우 레벨 무선 드라이버는 여전히 몇 가지 유틸리티 함수에 약간의 의존성을 가지고 있습니다. 드라이버 파일을 조금 수정하면 이러한 의존성을 쉽게 제거할 수 있지만, 이 파일들을 프로젝트에 포함시키는 것도 쉽습니다.

  1. File > New > Source Folder를 선택한 후, 폴더 이름을 "Utilities"로 지정하고 Finish를 클릭합니다.

stm32cubeide_newSourceFolder

  1. Project Explorer에서 Utilities 폴더를 마우스 오른쪽 클릭하여 **Import…**를 선택합니다. 마법사가 나타나면 General 카테고리에서 File System을 선택한 후 Next를 클릭합니다.

  2. From directory 필드를 STM32CubeWL MCU 패키지의 Utilities 디렉토리 위치로 선택합니다 (즉, <압축 해제 위치>/STM32Cube_FW_WL_V1.1.0/Utilities). 파일 탐색기 창에서 다음 파일들을 선택합니다:

    • Utilities/conf/utilities_conf_template.h
    • Utiliites/misc/stm32_mem.c
    • Utilities/misc/stm32_mem.h

해당 과정이 아래 두 그림에 나와 있습니다.

![stm32cubeide_importUtilitiesConf|379x500](upload://4CnyHHmN0vtqMGabwXqwFaU1bM5.png) ![stm32cubeide_importUtilitiesMem|379x500](upload://12iRZPiSb0TZh2c4XVIMKMm8Hxt.png)
  1. Project Explorer에서 utilities_conf_template.h라는 이름의 임포트한 파일을 찾아 utilities_conf.h로 변경합니다. 이제 프로젝트 구조는 아래처럼 보여야 합니다.

stm32cubeide_projectExplorerUtilities

  1. Utilities/confUtilites/misc 디렉토리 둘 모두에 단계 4를 반복하여 include 경로 목록에 추가합니다.

무선 드라이버

의존성을 처리하면 무선 드라이버 자체를 추가할 수 있습니다!

  1. Drivers 디렉토리에 “Radio”(또는 선호하는 이름)라는 이름의 새 폴더를 만듭니다. 해당 폴더에 STM32CubeWL MCU 패키지의 다음 파일들을 복사합니다:

    • <압축 해제 위치>/STM32Cube_FW_WL_V1.1.0/Middlewares/Third_Party/SubGHz_Phy/stm32_radio_driver/radio_driver.c
    • <압축 해제 위치>/STM32Cube_FW_WL_V1.1.0/Middlewares/Third_Party/SubGHz_Phy/stm32_radio_driver/radio_driver.h
    • <압축 해제 위치>/STM32Cube_FW_WL_V1.1.0/Middlewares/Third_Party/SubGHz_Phy/Conf/radio_conf_template.h
    • <압축 해제 위치>/STM32Cube_FW_WL_V1.1.0/Projects/NUCLEO-WL55JC/Applications/SubGHz_Phy/SubGHz_Phy_PingPong/SubGHz_Phy/Target/radio_board_if.c
    • <압축 해제 위치>/STM32Cube_FW_WL_V1.1.0/Projects/NUCLEO-WL55JC/Applications/SubGHz_Phy/SubGHz_Phy_PingPong/SubGHz_Phy/Target/radio_board_if.h
  2. radio_conf_template.h 파일의 이름을 radio_conf.h로 변경합니다. 이제 프로젝트 구조는 아래처럼 보여야 합니다.

  1. radio_conf.h 파일을 열고 mw_log_conf.h, utilities_def.h 그리고 sys_debug.h에 대한 include 디렉티브를 주석 처리합니다. subghz.h에 대한 include 디렉티브를 추가합니다.

stm32cubeide_editRadioConfH_edit

  1. 마찬가지로, radio_driver.c 파일을 열고 mw_log_conf.h에 대한 include 디렉티브를 주석 처리합니다.

  2. Drivers/Radio 디렉토리에 대해 단계 4를 반복하여 include 경로 목록에 추가합니다.

  3. <압축 해제 위치>/STM32Cube_FW_WL_V1.1.0/Projects/NUCLEO-WL55JC/Applications/SubGHz_Phy/SubGHz_Phy_PingPong/Core/Inc/platform.h 파일을 Core/Inc 디렉토리에 복사합니다.

  1. platform.h를 열고 stm32wlxx_ll_gpio.h에 대한 include 디렉티브를 주석 처리합니다.

  2. 마지막으로, 무선 드라이버 사용을 시작하려면 main.c 상단의 알맞는 user code 영역내에 #include "radio_driver.h" 행을 추가합니다.

stm32cubeide_editMainC_edit

응용 프로그램 예제

로우 레벨 Sub-GHz 파이 드라이버를 독립적으로 사용하는 예로서 두 개의 예제 프로그램을 만들었으며, 깃허브 리포지토리에서 확인할 수 있습니다. 이 예제들은 STM32CubeWL MCU Package에 있는 SubGHz_Phy_PingPong 예제의 하이 레벨 함수를 복제합니다. 즉, 둘 다 그림 1에 표시된 상태 기계(state machine)를 구현합니다. 두 예제의 유일한 차이점은 하나는 LoRa 모뎀을 사용하고 다른 하나는 FSK 모뎀을 사용한다는 것입니다.


그림 1: 로우 레벨 무선 드라이버 PingPong 예제 프로젝트의 유한 상태 기계

이 예제들을 실행하기 위해서는 두 개의 NUCLEO-WL55JC1 기판이 필요하며, 하나는 마스터 역할을 다른 하나는 슬레이브 역할을 할 것입니다. 처음에, 두 기판은 “PING” 메시지를 무작위한 간격으로 보내고 응답을 기다리는 마스터 상태에 있습니다. 최종적으로, 두 기판은 동기화되어 하나의 기판만 “PING” 메시지를 보내고 다른 기판은 응답으로 “PONG” 메시지를 보내게 됩니다. 응용 프로그램 실행을 위해, 이전 섹션에서 제공된 단계들을 따라 로우 레벨 Sub-GHz 무선 드라이버가 포함된 프로젝트를 생성하십시오.

그런 다음, 프로젝트 main.c 파일의 내용을 깃허브 리포지토리에 있는 파일 중 하나의 내용으로 바꾸기만 하면 됩니다. 마지막으로, 프로젝트를 빌드하여 두 Nucleo 기판을 프로그래밍하는 데 사용합니다.



이 예제들은 SubGHz_Phy_PingPong 예제와 호환됩니다. 즉, 하나의 기판은 위의 응용 프로그램으로 그리고 다른 기판은 SubGHz_Phy_PingPong 응용 프로그램으로 프로그래밍 가능하며, 이렇게 해도 둘 모두 예상대로 동작할 것입니다. 그러나 GFSK 변조를 사용하기 위해서는 SubGHz_Phy_PingPong 예제를 우선 약간 수정해야 합니다. subghz_phy_app.h 파일을 열어서 첫 번째 define 디렉티브를 다음과 같이 변경합니다:

#define USE_MODEM_LORA  0 //1
#define USE_MODEM_FSK   1 //0

#define REGION_US915 //REGION_EU868

그런 다음, radio.c에서 RadioRandom() 함수를 찾아 RadioSetModem( MODEM_LORA ); 라인을 주석 처리합니다. 난수를 얻는데 이 라인은 필요 없을 뿐만 아니라, 사전 초기화 단계에서 무선 설정 집합을 지웁니다. 따라서 이 경우에는 버그로 여겨져 포함되어서는 안 됩니다. 이제 SubGHz_Phy_PingPong 예제를 NUCLEO-WL55JC1 기판 중 하나에 컴파일해서 플래시할 준비가 되었습니다. 다른 기판 하나는 깃허브 리포지토리에 있는 main_gfsk.c 파일의 내용을 사용해 위의 설명에 따라 프로그래밍해야 합니다.



그림 1의 유한 상태 기계를 초기화 및 실행하기 전, 아래 목록 1에 정의된 radioInit() 함수를 호출함으로써 무선을 초기화합니다. 이 함수는 하나를 제외하고는 SubGHz_Phy_PingPong 예제와 동일한 무선 설정을 사용합니다. 참조 설명서의 6.1장 끝 부분에 다음과 같이 명시되어 있습니다:

The SMPS needs a clock to be functional. If for any reason this clock stops, the device may be destroyed. To avoid this situation, a clock detection is used to, in case of a clock failure, switch off the SMPS and enable the LDO. The SMPS clock detection is enabled by the sub-GHz radio SUBGHZ_SMPSC0R.CLKDE. By default, the SMPS clock detection is disabled and must be enabled before enabling the SMPS.

이러한 경고에도 불구하고, Sub-GHz 파이 미들웨어의 하이 레벨 레이어와 로우 레벨 레이어 모두가 SMPS 클럭 감지를 굳이 활성화하지 않습니다. radio_config.hDCDC_ENABLE이 정의되어 있어서, SUBGRF_SetRegulatorMode() 함수가 SMPS 스텝-다운 컨버터를 활성화시킬 것입니다. 따라서 이 함수 호출 바로 직전에 SMPS 클럭 감지가 수동으로 활성화됩니다.

목록 1: main_gfsk.cradioInit() 정의

void radioInit(void)
{
  // Initialize the hardware (SPI bus, TCXO control, RF switch)
  SUBGRF_Init(RadioOnDioIrq);

  // Use DCDC converter if `DCDC_ENABLE` is defined in radio_conf.h
  // "By default, the SMPS clock detection is disabled and must be enabled before enabling the SMPS." (6.1 in RM0453)
  SUBGRF_WriteRegister(SUBGHZ_SMPSC0R, (SUBGRF_ReadRegister(SUBGHZ_SMPSC0R) | SMPS_CLK_DET_ENABLE));
  SUBGRF_SetRegulatorMode();

  // Use the whole 256-byte buffer for both TX and RX
  SUBGRF_SetBufferBaseAddress(0x00, 0x00);

  SUBGRF_SetRfFrequency(RF_FREQUENCY);
  SUBGRF_SetRfTxPower(TX_OUTPUT_POWER);
  SUBGRF_SetStopRxTimerOnPreambleDetect(false);

  SUBGRF_SetPacketType(PACKET_TYPE_GFSK);

  ModulationParams_t modulationParams;
  modulationParams.PacketType = PACKET_TYPE_GFSK;
  modulationParams.Params.Gfsk.Bandwidth = SUBGRF_GetFskBandwidthRegValue(FSK_BANDWIDTH);
  modulationParams.Params.Gfsk.BitRate = FSK_DATARATE;
  modulationParams.Params.Gfsk.Fdev = FSK_FDEV;
  modulationParams.Params.Gfsk.ModulationShaping = MOD_SHAPING_G_BT_1;
  SUBGRF_SetModulationParams(&modulationParams);

  packetParams.PacketType = PACKET_TYPE_GFSK;
  packetParams.Params.Gfsk.AddrComp = RADIO_ADDRESSCOMP_FILT_OFF;
  packetParams.Params.Gfsk.CrcLength = RADIO_CRC_2_BYTES_CCIT;
  packetParams.Params.Gfsk.DcFree = RADIO_DC_FREEWHITENING;
  packetParams.Params.Gfsk.HeaderType = RADIO_PACKET_VARIABLE_LENGTH;
  packetParams.Params.Gfsk.PayloadLength = 0xFF;
  packetParams.Params.Gfsk.PreambleLength = (FSK_PREAMBLE_LENGTH << 3); // bytes to bits
  packetParams.Params.Gfsk.PreambleMinDetect = RADIO_PREAMBLE_DETECTOR_08_BITS;
  packetParams.Params.Gfsk.SyncWordLength = (FSK_SYNCWORD_LENGTH << 3); // bytes to bits
  SUBGRF_SetPacketParams(&packetParams);

  SUBGRF_SetSyncWord((uint8_t[]){0xC1, 0x94, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00});
  SUBGRF_SetWhiteningSeed(0x01FF);
}

로우 레벨 무선 드라이버를 사용하는 또 다른 함수는 상태 입력 함수입니다. 패킷을 주고 받는데 이러한 드라이버 함수를 사용하는 예로서, enterMasterRx()enterMasterTx()가 목록 2와 3 각각에 제공되어 있습니다. SUBGRF_SetDioIrqParams() 함수 사용에 대한 자세한 설명은 STM32WL 계열에서의 Sub-GHz 무선 인터럽트를 참고 바랍니다.

목록 2: main_gfsk.centerMasterRx() 정의

void enterMasterRx(pingPongFSM_t *const fsm)
{
  HAL_UART_Transmit(&huart2, "Master Rx start\r\n", 17, HAL_MAX_DELAY);
  SUBGRF_SetDioIrqParams( IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT | IRQ_CRC_ERROR,
                          IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT | IRQ_CRC_ERROR,
                          IRQ_RADIO_NONE,
                          IRQ_RADIO_NONE );
  SUBGRF_SetSwitch(RFO_LP, RFSWITCH_RX);
  packetParams.Params.Gfsk.PayloadLength = 0xFF;
  SUBGRF_SetPacketParams(&packetParams);
  SUBGRF_SetRx(fsm->rxTimeout << 6);
}

목록 3: main_gfsk.centerMasterTx() 정의

void enterMasterTx(pingPongFSM_t *const fsm)
{
  HAL_Delay(fsm->rxMargin);

  HAL_UART_Transmit(&huart2, "...PING\r\n", 9, HAL_MAX_DELAY);
  HAL_UART_Transmit(&huart2, "Master Tx start\r\n", 17, HAL_MAX_DELAY);
  SUBGRF_SetDioIrqParams( IRQ_TX_DONE | IRQ_RX_TX_TIMEOUT,
                          IRQ_TX_DONE | IRQ_RX_TX_TIMEOUT,
                          IRQ_RADIO_NONE,
                          IRQ_RADIO_NONE );
  SUBGRF_SetSwitch(RFO_LP, RFSWITCH_TX);
  // Workaround 5.1 in DS.SX1261-2.W.APP (before each packet transmission)
  SUBGRF_WriteRegister(0x0889, (SUBGRF_ReadRegister(0x0889) | 0x04));
  packetParams.Params.Gfsk.PayloadLength = 0x4;
  SUBGRF_SetPacketParams(&packetParams);
  SUBGRF_SendPayload((uint8_t *)"PING", 4, 0);
}

다시 한번, 깃허브 리포지토리에 있는 LoRa와 GFSK 변조 방식에 대한 전체 예제 코드를 확인해 보시기 바랍니다.



영문 원본: Using the Low-Level Sub-GHz Radio Driver for the STM32WL Series