STM32WLE5之LoRa# 系列3、易智联LM401 评估板PingPong应用移植
前言
前面我们已经做CubeMX和CubeIDE的工程移植,下面我们就将具体的应用程序进行修改
一、系统初始化SYS_APP.c文件
这个文件是系统应用的初始化,包括RTC、UART、低功耗等的初始化与配置。这里我们屏蔽掉DBG_Disable()和DBG_ProbesInit()两个函数。如下所示:
void SystemApp_Init(void)
{
/* USER CODE BEGIN SystemApp_Init_1 */
/* USER CODE END SystemApp_Init_1 */
/* Ensure that MSI is wake-up system clock */
__HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_MSI);
/*Initialize timer and RTC*/
UTIL_TIMER_Init();
/* Debug config : disable serial wires and DbgMcu pins settings */
//DBG_Disable();
/* Initializes the SW probes pins and the monitor RF pins via Alternate Function */
//DBG_ProbesInit();
/*Initialize the terminal */
UTIL_ADV_TRACE_Init();
UTIL_ADV_TRACE_RegisterTimeStampFunction(TimestampNow);
二、中断stm32wlxx_it.c文件
在文件的后面增加按键的中断函数
/* USER CODE BEGIN 1 */
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);;
}
void EXTI1_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
}
void EXTI4_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
}
/* USER CODE END 1 */
三、PingPong的应用程序
将PingPong的应用程序覆盖subghz_phy_app.c的内容
/*!
* \file subghz_phy_app.c
*
* \brief Ping-Pong implementation
*
* \copyright Revised BSD License, see section \ref LICENSE.
*
* \code
* ______ _
* / _____) _ | |
* ( (____ _____ ____ _| |_ _____ ____| |__
* \____ \| ___ | (_ _) ___ |/ ___) _ \
* _____) ) ____| | | || |_| ____( (___| | | |
* (______/|_____)_|_|_| \__)_____)\____)_| |_|
* (C)2013-2017 Semtech
*
* \endcode
*
* \author Miguel Luis ( Semtech )
*
* \author Gregory Cristian ( Semtech )
*/
/**
******************************************************************************
*
* Portions COPYRIGHT 2020 STMicroelectronics
*
* @file subghz_phy_app.c
* @author MCD Application Team
* @brief Application of the SubGHz_Phy Middleware
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "platform.h"
#include "sys_app.h"
#include "subghz_phy_app.h"
#include "radio.h"
#include "app_version.h"
/* USER CODE BEGIN Includes */
#include "stm32_timer.h"
#include "stm32_seq.h"
#include "utilities_def.h"
/* USER CODE END Includes */
/* External variables ---------------------------------------------------------*/
/* USER CODE BEGIN EV */
/* USER CODE END EV */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef enum
{
RX,
RX_TIMEOUT,
RX_ERROR,
TX,
TX_TIMEOUT,
} States_t;
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* Configurations */
/*Timeout*/
#define RX_TIMEOUT_VALUE 5000
#define TX_TIMEOUT_VALUE 3000
/* PING string*/
#define PING "PING"
/* PONG string*/
#define PONG "PONG"
/*Size of the payload to be sent*/
/* Size must be greater of equal the PING and PONG*/
#define MAX_APP_BUFFER_SIZE 255
#if (PAYLOAD_LEN > MAX_APP_BUFFER_SIZE)
#error PAYLOAD_LEN must be less or equal than MAX_APP_BUFFER_SIZE
#endif /* (PAYLOAD_LEN > MAX_APP_BUFFER_SIZE) */
/* wait for remote to be in Rx, before sending a Tx frame*/
#define RX_TIME_MARGIN 200
/* Afc bandwidth in Hz */
#define FSK_AFC_BANDWIDTH 83333
/* LED blink Period*/
#define LED_PERIOD_MS 200
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* Radio events function pointer */
static RadioEvents_t RadioEvents;
/* USER CODE BEGIN PV */
/*Ping Pong FSM states */
static States_t State = RX;
/* App Rx Buffer*/
static uint8_t BufferRx[MAX_APP_BUFFER_SIZE];
/* App Tx Buffer*/
static uint8_t BufferTx[MAX_APP_BUFFER_SIZE];
/* Last Received Buffer Size*/
uint16_t RxBufferSize = 0;
/* Last Received packer Rssi*/
int8_t RssiValue = 0;
/* Last Received packer SNR (in Lora modulation)*/
int8_t SnrValue = 0;
/* Led Timers objects*/
static UTIL_TIMER_Object_t timerLed;
/* device state. Master: true, Slave: false*/
bool isMaster = true;
/* random delay to make sure 2 devices will sync*/
/* the closest the random delays are, the longer it will
take for the devices to sync when started simultaneously*/
static int32_t random_delay;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/*!
* @brief Function to be executed on Radio Tx Done event
*/
static void OnTxDone(void);
/**
* @brief Function to be executed on Radio Rx Done event
* @param payload ptr of buffer received
* @param size buffer size
* @param rssi
* @param LoraSnr_FskCfo
*/
static void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t LoraSnr_FskCfo);
/**
* @brief Function executed on Radio Tx Timeout event
*/
static void OnTxTimeout(void);
/**
* @brief Function executed on Radio Rx Timeout event
*/
static void OnRxTimeout(void);
/**
* @brief Function executed on Radio Rx Error event
*/
static void OnRxError(void);
/* USER CODE BEGIN PFP */
/**
* @brief Function executed on when led timer elapses
* @param context ptr of LED context
*/
static void OnledEvent(void *context);
/**
* @brief PingPong state machine implementation
*/
static void PingPong_Process(void);
/* USER CODE END PFP */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch (GPIO_Pin)
{
case BUTTON_SW1_PIN:
BSP_LED_Toggle(LED_BLUE) ;
HAL_Delay(20);
BSP_LED_Toggle(LED_BLUE) ;
APP_PRINTF("BUTTON SW1\r\n");
break;
case BUTTON_SW2_PIN:
BSP_LED_Toggle(LED_GREEN) ;
HAL_Delay(20);
BSP_LED_Toggle(LED_GREEN) ;
APP_PRINTF("BUTTON SW2\r\n");
break;
case BUTTON_SW3_PIN:
BSP_LED_Toggle(LED_RED) ;
HAL_Delay(20);
BSP_LED_Toggle(LED_RED) ;
APP_PRINTF("BUTTON SW3\r\n");
break;
default:
APP_PRINTF("Unkonw Button\r\n");
break;
}
}
/* Exported functions ---------------------------------------------------------*/
void SubghzApp_Init(void)
{
/* USER CODE BEGIN SubghzApp_Init_1 */
APP_LOG(TS_OFF, VLEVEL_M, "\n\rPING PONG\n\r");
/* Print APP version*/
APP_LOG(TS_OFF, VLEVEL_M, "APP_VERSION= V%X.%X.%X\r\n",
(uint8_t)(__APP_VERSION >> __APP_VERSION_MAIN_SHIFT),
(uint8_t)(__APP_VERSION >> __APP_VERSION_SUB1_SHIFT),
(uint8_t)(__APP_VERSION >> __APP_VERSION_SUB2_SHIFT));
/* Led Timers*/
UTIL_TIMER_Create(&timerLed, 0xFFFFFFFFU, UTIL_TIMER_ONESHOT, OnledEvent, NULL);
UTIL_TIMER_SetPeriod(&timerLed, LED_PERIOD_MS);
UTIL_TIMER_Start(&timerLed);
/* USER CODE END SubghzApp_Init_1 */
/* Radio initialization */
RadioEvents.TxDone = OnTxDone;
RadioEvents.RxDone = OnRxDone;
RadioEvents.TxTimeout = OnTxTimeout;
RadioEvents.RxTimeout = OnRxTimeout;
RadioEvents.RxError = OnRxError;
Radio.Init(&RadioEvents);
/* USER CODE BEGIN SubghzApp_Init_2 */
/* Radio Set frequency */
Radio.SetChannel(RF_FREQUENCY);
/* Radio configuration */
#if ((USE_MODEM_LORA == 1) && (USE_MODEM_FSK == 0))
APP_LOG(TS_OFF, VLEVEL_M, "---------------\n\r");
APP_LOG(TS_OFF, VLEVEL_M, "LORA_MODULATION\n\r");
APP_LOG(TS_OFF, VLEVEL_M, "LORA_BW=%d kHz\n\r", (1 << LORA_BANDWIDTH) * 125);
APP_LOG(TS_OFF, VLEVEL_M, "LORA_SF=%d\n\r", LORA_SPREADING_FACTOR);
Radio.SetTxConfig(MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
LORA_SPREADING_FACTOR, LORA_CODINGRATE,
LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
true, 0, 0, LORA_IQ_INVERSION_ON, TX_TIMEOUT_VALUE);
Radio.SetRxConfig(MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
0, true, 0, 0, LORA_IQ_INVERSION_ON, true);
Radio.SetMaxPayloadLength(MODEM_LORA, MAX_APP_BUFFER_SIZE);
#elif ((USE_MODEM_LORA == 0) && (USE_MODEM_FSK == 1))
APP_LOG(TS_OFF, VLEVEL_M, "---------------\n\r");
APP_LOG(TS_OFF, VLEVEL_M, "FSK_MODULATION\n\r");
APP_LOG(TS_OFF, VLEVEL_M, "FSK_BW=%d Hz\n\r", FSK_BANDWIDTH);
APP_LOG(TS_OFF, VLEVEL_M, "FSK_DR=%d bits/s\n\r", FSK_DATARATE);
Radio.SetTxConfig(MODEM_FSK, TX_OUTPUT_POWER, FSK_FDEV, 0,
FSK_DATARATE, 0,
FSK_PREAMBLE_LENGTH, FSK_FIX_LENGTH_PAYLOAD_ON,
true, 0, 0, 0, TX_TIMEOUT_VALUE);
Radio.SetRxConfig(MODEM_FSK, FSK_BANDWIDTH, FSK_DATARATE,
0, FSK_AFC_BANDWIDTH, FSK_PREAMBLE_LENGTH,
0, FSK_FIX_LENGTH_PAYLOAD_ON, 0, true,
0, 0, false, true);
Radio.SetMaxPayloadLength(MODEM_FSK, MAX_APP_BUFFER_SIZE);
#else
#error "Please define a modulation in the subghz_phy_app.h file."
#endif /* USE_MODEM_LORA | USE_MODEM_FSK */
/* LED initialization*/
BSP_LED_Init(LED_GREEN);
BSP_LED_Init(LED_RED);
BSP_LED_Init(LED_BLUE);
BSP_PB_Init(BUTTON_SW1, BUTTON_MODE_EXTI);
BSP_PB_Init(BUTTON_SW2, BUTTON_MODE_EXTI);
BSP_PB_Init(BUTTON_SW3, BUTTON_MODE_EXTI);
/*calculate random delay for synchronization*/
random_delay = (Radio.Random()) >> 22; /*10bits random e.g. from 0 to 1023 ms*/
/*fills tx buffer*/
memset(BufferTx, 0x0, MAX_APP_BUFFER_SIZE);
APP_LOG(TS_ON, VLEVEL_L, "rand=%d\n\r", random_delay);
/*starts reception*/
Radio.Rx(RX_TIMEOUT_VALUE + random_delay);
/*register task to to be run in while(1) after Radio IT*/
UTIL_SEQ_RegTask((1 << CFG_SEQ_Task_SubGHz_Phy_App_Process), UTIL_SEQ_RFU, PingPong_Process);
/* USER CODE END SubghzApp_Init_2 */
}
/* USER CODE BEGIN EF */
/* USER CODE END EF */
/* Private functions ---------------------------------------------------------*/
static void OnTxDone(void)
{
/* USER CODE BEGIN OnTxDone */
APP_LOG(TS_ON, VLEVEL_L, "OnTxDone\n\r");
/* Update the State of the FSM*/
State = TX;
/* Run PingPong process in background*/
UTIL_SEQ_SetTask((1 << CFG_SEQ_Task_SubGHz_Phy_App_Process), CFG_SEQ_Prio_0);
/* USER CODE END OnTxDone */
}
static void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t LoraSnr_FskCfo)
{
/* USER CODE BEGIN OnRxDone */
APP_LOG(TS_ON, VLEVEL_L, "OnRxDone\n\r");
#if ((USE_MODEM_LORA == 1) && (USE_MODEM_FSK == 0))
APP_LOG(TS_ON, VLEVEL_L, "RssiValue=%d dBm, SnrValue=%ddB\n\r", rssi, LoraSnr_FskCfo);
/* Record payload Signal to noise ratio in Lora*/
SnrValue = LoraSnr_FskCfo;
#endif /* USE_MODEM_LORA | USE_MODEM_FSK */
#if ((USE_MODEM_LORA == 0) && (USE_MODEM_FSK == 1))
APP_LOG(TS_ON, VLEVEL_L, "RssiValue=%d dBm, Cfo=%dkHz\n\r", rssi, LoraSnr_FskCfo);
SnrValue = 0; /*not applicable in GFSK*/
#endif /* USE_MODEM_LORA | USE_MODEM_FSK */
/* Update the State of the FSM*/
State = RX;
/* Clear BufferRx*/
memset(BufferRx, 0, MAX_APP_BUFFER_SIZE);
/* Record payload size*/
RxBufferSize = size;
if (RxBufferSize <= MAX_APP_BUFFER_SIZE)
{
memcpy(BufferRx, payload, RxBufferSize);
}
/* Record Received Signal Strength*/
RssiValue = rssi;
/* Record payload content*/
APP_LOG(TS_ON, VLEVEL_H, "payload. size=%d \n\r", size);
for (int i = 0; i < PAYLOAD_LEN; i++)
{
APP_LOG(TS_OFF, VLEVEL_H, "%02X", BufferRx[i]);
if (i % 16 == 15)
{
APP_LOG(TS_OFF, VLEVEL_H, "\n\r");
}
}
APP_LOG(TS_OFF, VLEVEL_H, "\n\r");
/* Run PingPong process in background*/
UTIL_SEQ_SetTask((1 << CFG_SEQ_Task_SubGHz_Phy_App_Process), CFG_SEQ_Prio_0);
/* USER CODE END OnRxDone */
}
static void OnTxTimeout(void)
{
/* USER CODE BEGIN OnTxTimeout */
APP_LOG(TS_ON, VLEVEL_L, "OnTxTimeout\n\r");
/* Update the State of the FSM*/
State = TX_TIMEOUT;
/* Run PingPong process in background*/
UTIL_SEQ_SetTask((1 << CFG_SEQ_Task_SubGHz_Phy_App_Process), CFG_SEQ_Prio_0);
/* USER CODE END OnTxTimeout */
}
static void OnRxTimeout(void)
{
/* USER CODE BEGIN OnRxTimeout */
APP_LOG(TS_ON, VLEVEL_L, "OnRxTimeout\n\r");
/* Update the State of the FSM*/
State = RX_TIMEOUT;
/* Run PingPong process in background*/
UTIL_SEQ_SetTask((1 << CFG_SEQ_Task_SubGHz_Phy_App_Process), CFG_SEQ_Prio_0);
/* USER CODE END OnRxTimeout */
}
static void OnRxError(void)
{
/* USER CODE BEGIN OnRxError */
APP_LOG(TS_ON, VLEVEL_L, "OnRxError\n\r");
/* Update the State of the FSM*/
State = RX_ERROR;
/* Run PingPong process in background*/
UTIL_SEQ_SetTask((1 << CFG_SEQ_Task_SubGHz_Phy_App_Process), CFG_SEQ_Prio_0);
/* USER CODE END OnRxError */
}
/* USER CODE BEGIN PrFD */
static void PingPong_Process(void)
{
Radio.Sleep();
switch (State)
{
case RX:
if (isMaster == true)
{
if (RxBufferSize > 0)
{
if (strncmp((const char *)BufferRx, PONG, sizeof(PONG) - 1) == 0)
{
UTIL_TIMER_Stop(&timerLed);
/* switch off green led */
BSP_LED_Off(LED_GREEN);
/* master toggles red led */
BSP_LED_Toggle(LED_RED);
/* Add delay between RX and TX */
HAL_Delay(Radio.GetWakeupTime() + RX_TIME_MARGIN);
/* master sends PING*/
APP_LOG(TS_ON, VLEVEL_L, "..."
"PING"
"\n\r");
APP_LOG(TS_ON, VLEVEL_L, "Master Tx start\n\r");
memcpy(BufferTx, PING, sizeof(PING) - 1);
Radio.Send(BufferTx, PAYLOAD_LEN);
}
else if (strncmp((const char *)BufferRx, PING, sizeof(PING) - 1) == 0)
{
/* A master already exists then become a slave */
isMaster = false;
APP_LOG(TS_ON, VLEVEL_L, "Slave Rx start\n\r");
Radio.Rx(RX_TIMEOUT_VALUE);
}
else /* valid reception but neither a PING or a PONG message */
{
/* Set device as master and start again */
isMaster = true;
APP_LOG(TS_ON, VLEVEL_L, "Master Rx start\n\r");
Radio.Rx(RX_TIMEOUT_VALUE);
}
}
}
else
{
if (RxBufferSize > 0)
{
if (strncmp((const char *)BufferRx, PING, sizeof(PING) - 1) == 0)
{
UTIL_TIMER_Stop(&timerLed);
/* switch off red led */
BSP_LED_Off(LED_RED);
/* slave toggles green led */
BSP_LED_Toggle(LED_GREEN);
/* Add delay between RX and TX */
HAL_Delay(Radio.GetWakeupTime() + RX_TIME_MARGIN);
/*slave sends PONG*/
APP_LOG(TS_ON, VLEVEL_L, "..."
"PONG"
"\n\r");
APP_LOG(TS_ON, VLEVEL_L, "Slave Tx start\n\r");
memcpy(BufferTx, PONG, sizeof(PONG) - 1);
Radio.Send(BufferTx, PAYLOAD_LEN);
}
else /* valid reception but not a PING as expected */
{
/* Set device as master and start again */
isMaster = true;
APP_LOG(TS_ON, VLEVEL_L, "Master Rx start\n\r");
Radio.Rx(RX_TIMEOUT_VALUE);
}
}
}
break;
case TX:
APP_LOG(TS_ON, VLEVEL_L, "Rx start\n\r");
Radio.Rx(RX_TIMEOUT_VALUE);
break;
case RX_TIMEOUT:
#if defined (LOW_POWER_DISABLE) && (LOW_POWER_DISABLE == 0)
//UTIL_TIMER_SetPeriod(&timerLed, LED_PERIOD_MS*20);
UTIL_TIMER_Stop(&timerLed);
BSP_LED_Off(LED_RED) ;
BSP_LED_Off(LED_GREEN) ;
Radio.Sleep();
break;
#endif
case RX_ERROR:
if (isMaster == true)
{
/* Send the next PING frame */
/* Add delay between RX and TX*/
/* add random_delay to force sync between boards after some trials*/
HAL_Delay(Radio.GetWakeupTime() + RX_TIME_MARGIN + random_delay);
APP_LOG(TS_ON, VLEVEL_L, "Master Tx start\n\r");
/* master sends PING*/
memcpy(BufferTx, PING, sizeof(PING) - 1);
Radio.Send(BufferTx, PAYLOAD_LEN);
}
else
{
APP_LOG(TS_ON, VLEVEL_L, "Slave Rx start\n\r");
Radio.Rx(RX_TIMEOUT_VALUE);
}
break;
case TX_TIMEOUT:
APP_LOG(TS_ON, VLEVEL_L, "Slave Rx start\n\r");
Radio.Rx(RX_TIMEOUT_VALUE);
break;
default:
break;
}
}
static void OnledEvent(void *context)
{
BSP_LED_Toggle(LED_GREEN);
BSP_LED_Toggle(LED_RED);
UTIL_TIMER_Start(&timerLed);
}
/* USER CODE END PrFD */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
至此,PingPong例程全部功能移植完成。
四、烧写测试
通过编译烧写,我们能看到如下输出
再烧写硬外一个评估板,并上电运行我们能够看到如下输出日志
五、Lora相关参数配置
默认的Lora参数位于subghz_phy_app.h中的宏定义。
#define USE_MODEM_LORA 1
#define USE_MODEM_FSK 0
#define REGION_CN470
#if defined( REGION_AS923 )
#define RF_FREQUENCY 923000000 /* Hz */
#elif defined( REGION_AU915 )
#define RF_FREQUENCY 915000000 /* Hz */
#elif defined( REGION_CN470 )
#define RF_FREQUENCY 470000000 /* Hz */
#elif defined( REGION_CN779 )
#define RF_FREQUENCY 779000000 /* Hz */
#elif defined( REGION_EU433 )
#define RF_FREQUENCY 433000000 /* Hz */
#elif defined( REGION_EU868 )
#define RF_FREQUENCY 868000000 /* Hz */
#elif defined( REGION_KR920 )
#define RF_FREQUENCY 920000000 /* Hz */
#elif defined( REGION_IN865 )
#define RF_FREQUENCY 865000000 /* Hz */
#elif defined( REGION_US915 )
#define RF_FREQUENCY 915000000 /* Hz */
#elif defined( REGION_RU864 )
#define RF_FREQUENCY 864000000 /* Hz */
#else
#error "Please define a frequency band in the compiler options."
#endif /* REGION_XXxxx */
#define TX_OUTPUT_POWER 14 /* dBm */
#if (( USE_MODEM_LORA == 1 ) && ( USE_MODEM_FSK == 0 ))
#define LORA_BANDWIDTH 0 /* [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved] */
#define LORA_SPREADING_FACTOR 7 /* [SF7..SF12] */
#define LORA_CODINGRATE 1 /* [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8] */
#define LORA_PREAMBLE_LENGTH 8 /* Same for Tx and Rx */
#define LORA_SYMBOL_TIMEOUT 5 /* Symbols */
#define LORA_FIX_LENGTH_PAYLOAD_ON false
#define LORA_IQ_INVERSION_ON false
#elif (( USE_MODEM_LORA == 0 ) && ( USE_MODEM_FSK == 1 ))
#define FSK_FDEV 25000 /* Hz */
#define FSK_DATARATE 50000 /* bps */
#define FSK_BANDWIDTH 50000 /* Hz */
#define FSK_PREAMBLE_LENGTH 5 /* Same for Tx and Rx */
#define FSK_FIX_LENGTH_PAYLOAD_ON false
#else
#error "Please define a modem in the compiler subghz_phy_app.h."
#endif /* USE_MODEM_LORA | USE_MODEM_FSK */
#define PAYLOAD_LEN 64
相关资源链接
通过1、易智联LM401 评估板简介;2、易智联LM401 评估板PingPong工程移植;3、易智联LM401 评估板PingPong应用移植;这个3个篇幅我们完成了从基本硬件到基于CubeMX的框架工程再到PingPong收发例程的移植。至此我们有了一个基本STM32WLE5的基本运行环境。
文件中提供的易智联Demo板连接:
https://item.taobao.com/item.htm?spm=a1z0k.7628869.0.0.4fbb1be2qSrsJg&id=655801203935&_u=t2dmg8j26111
文件中的CubeMX工程及BSP文件链接:
https://download.csdn.net/download/ww2801/76277784