基于STM32WB55 Nucleo与Open Thread协议栈的无线UDP通讯实验

基于STM32WB55 Nucleo与Open Thread协议栈的无线UDP通讯实验

Github

https://github.com/HaHaHaHaHaGe/OpenThreadUDP

简介

功能描述

完成后可实现两个NUCLEO-WB55RG开发板节点相互收发数据
使用的协议栈是OpenThread
发送数据的方式使用UDP广播方式

所需环境

下面的环境不是必须的,只是我在进行试验时所用到的环境,具体可根据自己的需求相应修改,但是实验步骤和结果不能保证一致。
软件部分:
STM32CubeMX 6.1.0
STM32CubeIDE 1.5.0
STM32CubeProgrammer 2.5.0
硬件部分:
NUCLEO-WB55RG

固件烧写

由于STM32WB55的RF部分是由M0核控制的,协议栈也是在M0里的,所以若想使用某个协议栈,就需要先去烧写对应的协议栈。
下载地址:https://github.com/STMicroelectronics/STM32CubeWB
固件目录:STM32CubeWB-master\STM32CubeWB-master\Projects\STM32WB_Copro_Wireless_Binaries\STM32WB5x
详细步骤:STM32CubeWB-master/STM32CubeWB-master/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x/Release_Notes.html
在这里插入图片描述
画箭头的地方是我们需要用到的文件以及文件的烧写地址

在烧写相应的协议栈之前,首先要将FUS升级到v1.1.0
(也就是需要烧写stm32wb5x_FUS_fw.bin)
但是不能直接烧写1.1.0,需要烧写1.0.2然后再烧写1.1.0版本

烧写玩FUS后,就可以烧写stm32wb5x_Thread_FTD_fw.bin协议栈了

PS:我在dongle与开发板测试发现,开发板无法使用usb模式只能使用stlink,只有dongle可以使用usb,可能是因为原厂没有为开发板加入dfu?

在这里插入图片描述
在这里插入图片描述
注意:如果使用stlink方式烧写固件,那么Read FUS State等按钮是灰色的。

如:在这里插入图片描述
固件烧写后,就可以开始下一步工作了

项目配置

创建好项目后,我们的主要目的是使能THREAD协议栈
在这里插入图片描述
但是想使用它,需要一些先决条件

启用THREAD的先决条件

1.System Core -> HSEM
2.System Core -> RCC 并且 HSE = 32Mhz
3.Timers -> RTC
4.Connectivity -> RF
5.Connectivity -> LPUART1
5.Middleware -> STM32_WPAN
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210119172548788.png

详细配置

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
取消串口的static选项很重要,否则会导致编译不过

然后为了方便看到现象,可以开启PB0与PB1,查看当前发射与接受状态
在这里插入图片描述

代码修改

main.c


/* USER CODE BEGIN Includes */
#include "stm32_lpm.h"
#include "stm32_seq.h"
#include "dbg_trace.h"
#include "hw_conf.h"
#include "otp.h"
/* USER CODE END Includes */

/* USER CODE BEGIN PV */
uint32_t child_notif = 0;
/* USER CODE END PV */

/* USER CODE BEGIN PFP */
void PeriphClock_Config(void);
static void Reset_Device( void );
static void Reset_IPCC( void );
static void Reset_BackupDomain( void );
static void Init_Exti( void );
static void Config_HSE(void);
/* USER CODE END PFP */

int main(void)
{
  /* USER CODE BEGIN 1 */
	__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
	Reset_Device();
	Config_HSE();
  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */
	PeriphClock_Config();
	Init_Exti();
  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_RF_Init();
  MX_RTC_Init();
  MX_LPUART1_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Init code for STM32_WPAN */
  APPE_Init();
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		if(child_notif == 1)
			UTIL_SEQ_SetTask(TASK_UDP_SEND, CFG_SCH_PRIO_1);
		UTIL_SEQ_Run( UTIL_SEQ_DEFAULT );
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}



/* USER CODE BEGIN 4 */
void PeriphClock_Config(void)
{
  #if (CFG_USB_INTERFACE_ENABLE != 0)
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = { 0 };
  RCC_CRSInitTypeDef RCC_CRSInitStruct = { 0 };

  /**
   * This prevents the CPU2 to disable the HSI48 oscillator when
   * it does not use anymore the RNG IP
   */
  LL_HSEM_1StepLock( HSEM, 5 );

  LL_RCC_HSI48_Enable();

  while(!LL_RCC_HSI48_IsReady());

  /* Select HSI48 as USB clock source */
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USB;
  PeriphClkInitStruct.UsbClockSelection = RCC_USBCLKSOURCE_HSI48;
  HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);

  /*Configure the clock recovery system (CRS)**********************************/

  /* Enable CRS Clock */
  __HAL_RCC_CRS_CLK_ENABLE();

  /* Default Synchro Signal division factor (not divided) */
  RCC_CRSInitStruct.Prescaler = RCC_CRS_SYNC_DIV1;

  /* Set the SYNCSRC[1:0] bits according to CRS_Source value */
  RCC_CRSInitStruct.Source = RCC_CRS_SYNC_SOURCE_USB;

  /* HSI48 is synchronized with USB SOF at 1KHz rate */
  RCC_CRSInitStruct.ReloadValue = RCC_CRS_RELOADVALUE_DEFAULT;
  RCC_CRSInitStruct.ErrorLimitValue = RCC_CRS_ERRORLIMIT_DEFAULT;

  RCC_CRSInitStruct.Polarity = RCC_CRS_SYNC_POLARITY_RISING;

  /* Set the TRIM[5:0] to the default value*/
  RCC_CRSInitStruct.HSI48CalibrationValue = RCC_CRS_HSI48CALIBRATION_DEFAULT;

  /* Start automatic synchronization */
  HAL_RCCEx_CRSConfig(&RCC_CRSInitStruct);
#endif

  return;
}
static void Config_HSE(void)
{
    OTP_ID0_t * p_otp;

  /**
   * Read HSE_Tuning from OTP
   */
  p_otp = (OTP_ID0_t *) OTP_Read(0);
  if (p_otp)
  {
    LL_RCC_HSE_SetCapacitorTuning(p_otp->hse_tuning);
  }

  return;
}


static void Reset_Device( void )
{
#if ( CFG_HW_RESET_BY_FW == 1 )
  Reset_BackupDomain();

  Reset_IPCC();
#endif

  return;
}

static void Reset_IPCC( void )
{
  LL_AHB3_GRP1_EnableClock(LL_AHB3_GRP1_PERIPH_IPCC);

  LL_C1_IPCC_ClearFlag_CHx(
      IPCC,
      LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4
      | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6);

  LL_C2_IPCC_ClearFlag_CHx(
      IPCC,
      LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4
      | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6);

  LL_C1_IPCC_DisableTransmitChannel(
      IPCC,
      LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4
      | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6);

  LL_C2_IPCC_DisableTransmitChannel(
      IPCC,
      LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4
      | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6);

  LL_C1_IPCC_DisableReceiveChannel(
      IPCC,
      LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4
      | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6);

  LL_C2_IPCC_DisableReceiveChannel(
      IPCC,
      LL_IPCC_CHANNEL_1 | LL_IPCC_CHANNEL_2 | LL_IPCC_CHANNEL_3 | LL_IPCC_CHANNEL_4
      | LL_IPCC_CHANNEL_5 | LL_IPCC_CHANNEL_6);

  return;
}

static void Reset_BackupDomain( void )
{
  if ((LL_RCC_IsActiveFlag_PINRST() != FALSE) && (LL_RCC_IsActiveFlag_SFTRST() == FALSE))
  {
    HAL_PWR_EnableBkUpAccess(); /**< Enable access to the RTC registers */

    /**
     *  Write twice the value to flush the APB-AHB bridge
     *  This bit shall be written in the register before writing the next one
     */
    HAL_PWR_EnableBkUpAccess();

    __HAL_RCC_BACKUPRESET_FORCE();
    __HAL_RCC_BACKUPRESET_RELEASE();
  }

  return;
}


static void Init_Exti( void )
{
  /**< Disable all wakeup interrupt on CPU1  except IPCC(36), HSEM(38) */
  LL_EXTI_DisableIT_0_31(~0);
  LL_EXTI_DisableIT_32_63( (~0) & (~(LL_EXTI_LINE_36 | LL_EXTI_LINE_38)) );

  return;
}
/* USER CODE END 4 */

app_conf.h



typedef enum
{
  CFG_TASK_MSG_FROM_M0_TO_M4,
  CFG_TASK_SEND_CLI_TO_M0,
  CFG_TASK_SYSTEM_HCI_ASYNCH_EVT,
#if (CFG_USB_INTERFACE_ENABLE != 0)
  CFG_TASK_VCP_SEND_DATA,
#endif /* (CFG_USB_INTERFACE_ENABLE != 0) */
  /* USER CODE BEGIN CFG_IdleTask_Id_t */
  CFG_TASK_UDP_SEND,
  /* USER CODE END CFG_IdleTask_Id_t */
  CFG_TASK_NBR  /**< Shall be last in the list */
} CFG_IdleTask_Id_t;

/* Scheduler types and defines        */
/*------------------------------------*/
#define TASK_MSG_FROM_M0_TO_M4      (1U << CFG_TASK_MSG_FROM_M0_TO_M4)
/* USER CODE BEGIN DEFINE_TASK */
#define TASK_UDP_SEND               (1U << CFG_TASK_UDP_SEND)
/* USER CODE END DEFINE_TASK */

app_thread.c


/* USER CODE BEGIN PM */
#define SuccessOrExit(aStatus) \
    do                         \
    {                          \
        if ((aStatus) != 0)    \
        {                      \
            goto exit;         \
        }                      \
    } while (false)

#define VerifyOrExit(aCondition, ...) \
    do                                \
    {                                 \
        if (!(aCondition))            \
        {                             \
            __VA_ARGS__;              \
            goto exit;                \
        }                             \
    } while (false)
/* USER CODE END PM */



/* USER CODE BEGIN PFP */
static otError UdpBind(uint16_t aPort);
static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
static void APP_THREAD_UdpSend(void);
static otError UdpSend(void);
/* USER CODE END PFP */



/* USER CODE BEGIN PV */
otUdpSocket mSocket;
uint8_t udpBufffer[256] = "HELLO UDP!";
uint16_t udpPort = 1234;

extern uint32_t child_notif;
/* USER CODE END PV */

/* Functions Definition ------------------------------------------------------*/

void APP_THREAD_Init( void )
{
  /* USER CODE BEGIN APP_THREAD_INIT_1 */

  /* USER CODE END APP_THREAD_INIT_1 */

  SHCI_CmdStatus_t ThreadInitStatus;

  /* Check the compatibility with the Coprocessor Wireless Firmware loaded */
  APP_THREAD_CheckWirelessFirmwareInfo();

#if (CFG_USB_INTERFACE_ENABLE != 0)
  VCP_Init(&VcpTxBuffer[0], &VcpRxBuffer[0]);
#endif /* (CFG_USB_INTERFACE_ENABLE != 0) */

  /* Register cmdbuffer */
  APP_THREAD_RegisterCmdBuffer(&ThreadOtCmdBuffer);

  /**
   * Do not allow standby in the application
   */
  UTIL_LPM_SetOffMode(1 << CFG_LPM_APP_THREAD, UTIL_LPM_DISABLE);

  /* Init config buffer and call TL_THREAD_Init */
  APP_THREAD_TL_THREAD_INIT();

  /* Configure UART for sending CLI command from M4 */
  APP_THREAD_Init_UART_CLI();

  /* Send Thread start system cmd to M0 */
  ThreadInitStatus = SHCI_C2_THREAD_Init();

  /* Prevent unused argument(s) compilation warning */
  UNUSED(ThreadInitStatus);

  /* Register task */
  /* Create the different tasks */
  UTIL_SEQ_RegTask( 1<<(uint32_t)CFG_TASK_MSG_FROM_M0_TO_M4, UTIL_SEQ_RFU, APP_THREAD_ProcessMsgM0ToM4);

  /* USER CODE BEGIN INIT TASKS */
	UTIL_SEQ_RegTask( 1<<(uint32_t)CFG_TASK_UDP_SEND, UTIL_SEQ_RFU, APP_THREAD_UdpSend);
  /* USER CODE END INIT TASKS */

  /* Initialize and configure the Thread device*/
  APP_THREAD_DeviceConfig();

  /* USER CODE BEGIN APP_THREAD_INIT_2 */

  /* USER CODE END APP_THREAD_INIT_2 */
}


static void APP_THREAD_DeviceConfig(void)
{
  otError error;
  error = otInstanceErasePersistentInfo(NULL);
  if (error != OT_ERROR_NONE)
  {
    APP_THREAD_Error(ERR_THREAD_ERASE_PERSISTENT_INFO,error);
  }
  otInstanceFinalize(NULL);
  otInstanceInitSingle();
  error = otSetStateChangedCallback(NULL, APP_THREAD_StateNotif, NULL);
  if (error != OT_ERROR_NONE)
  {
    APP_THREAD_Error(ERR_THREAD_SET_STATE_CB,error);
  }
  error = otLinkSetChannel(NULL, C_CHANNEL_NB);
  if (error != OT_ERROR_NONE)
  {
    APP_THREAD_Error(ERR_THREAD_SET_CHANNEL,error);
  }
  error = otLinkSetPanId(NULL, C_PANID);
  if (error != OT_ERROR_NONE)
  {
    APP_THREAD_Error(ERR_THREAD_SET_PANID,error);
  }
  error = otIp6SetEnabled(NULL, true);
  if (error != OT_ERROR_NONE)
  {
    APP_THREAD_Error(ERR_THREAD_IPV6_ENABLE,error);
  }
  error = otThreadSetEnabled(NULL, true);
  if (error != OT_ERROR_NONE)
  {
    APP_THREAD_Error(ERR_THREAD_START,error);
  }

  /* USER CODE BEGIN DEVICECONFIG */
	  /* Initialiaze socket */
  memset(&mSocket, 0, sizeof(mSocket));

  /* Open socket */
  otUdpOpen(NULL, &mSocket, HandleUdpReceive, NULL);
  UdpBind(udpPort);
  /* USER CODE END DEVICECONFIG */
}

/**
 * @brief Thread notification when the state changes.
 * @param  aFlags  : Define the item that has been modified
 *         aContext: Context
 *
 * @retval None
 */
static void APP_THREAD_StateNotif(uint32_t NotifFlags, void *pContext)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(pContext);

  /* USER CODE BEGIN APP_THREAD_STATENOTIF */

  /* USER CODE END APP_THREAD_STATENOTIF */

  if ((NotifFlags & (uint32_t)OT_CHANGED_THREAD_ROLE) == (uint32_t)OT_CHANGED_THREAD_ROLE)
  {
    switch (otThreadGetDeviceRole(NULL))
    {
    case OT_DEVICE_ROLE_DISABLED:
      /* USER CODE BEGIN OT_DEVICE_ROLE_DISABLED */

      /* USER CODE END OT_DEVICE_ROLE_DISABLED */
      break;
    case OT_DEVICE_ROLE_DETACHED:
      /* USER CODE BEGIN OT_DEVICE_ROLE_DETACHED */
	  child_notif = 0U;
      /* USER CODE END OT_DEVICE_ROLE_DETACHED */
      break;
    case OT_DEVICE_ROLE_CHILD:
      /* USER CODE BEGIN OT_DEVICE_ROLE_CHILD */
//			if (child_notif == 0)
//      {
//        HAL_Delay(3000U);
//        UTIL_SEQ_SetTask(TASK_UDP_SEND, CFG_SCH_PRIO_1);
//      }
      child_notif = 1U;
      /* USER CODE END OT_DEVICE_ROLE_CHILD */
      break;
    case OT_DEVICE_ROLE_ROUTER :
      /* USER CODE BEGIN OT_DEVICE_ROLE_ROUTER */

      /* USER CODE END OT_DEVICE_ROLE_ROUTER */
      break;
    case OT_DEVICE_ROLE_LEADER :
      /* USER CODE BEGIN OT_DEVICE_ROLE_LEADER */

      /* USER CODE END OT_DEVICE_ROLE_LEADER */
      break;
    default:
      /* USER CODE BEGIN DEFAULT */

      /* USER CODE END DEFAULT */
      break;
    }
  }
}


/* USER CODE BEGIN FD_LOCAL_FUNCTIONS */
/**
 * @brief This function initiates the APP_THREAD_UdpSend procedure
 *
 * @param None
 * @retval None
 */
static void APP_THREAD_UdpSend(void)
{
  HAL_Delay(100U);

  /* Send Udp request */
  UdpSend();
}

static otError UdpSend(void)
{
    otError       error;
    otMessageInfo messageInfo;
    otMessage *   message = NULL;

    memset(&messageInfo, 0, sizeof(messageInfo));

    error = otIp6AddressFromString("ff02::1", &messageInfo.mPeerAddr);
    SuccessOrExit(error);

    messageInfo.mPeerPort    = udpPort;
    messageInfo.mInterfaceId = OT_NETIF_INTERFACE_ID_THREAD;

    message = otUdpNewMessage(NULL, true);
    VerifyOrExit(message != NULL, error = OT_ERROR_NO_BUFS);

    error = otMessageAppend(message, udpBufffer, (uint16_t)strlen((const char*)udpBufffer));
    SuccessOrExit(error);

    APP_DBG("Sending UDP message %s", udpBufffer);
    error = otUdpSend(&mSocket, message, &messageInfo);

exit:

    if (error != OT_ERROR_NONE && message != NULL)
    {
        APP_DBG("UdpSend failed with error : %s", error);
        otMessageFree(message);
    }

    return error;
}
static uint16_t Swap16(uint16_t v)
{
    return (((v & 0x00ffU) << 8) & 0xff00) | (((v & 0xff00U) >> 8) & 0x00ff);
}

static uint16_t HostSwap16(uint16_t v)
{
    return Swap16(v);
}
static otError UdpBind(uint16_t aPort)
{
    otError    error;
    otSockAddr sockaddr;

    memset(&sockaddr, 0, sizeof(sockaddr));

    /* "::" specifies the IPv6 Unspecified Address */
    error = otIp6AddressFromString("::", &sockaddr.mAddress);
    SuccessOrExit(error);

    sockaddr.mPort    = aPort;
    sockaddr.mScopeId = OT_NETIF_INTERFACE_ID_THREAD;

    error = otUdpBind(&mSocket, &sockaddr);
	HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_0);
exit:
    return error;
}

static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
{
  int     length;
  uint8_t udpBuffferReceived[256];

  APP_DBG("Received %d bytes from ", otMessageGetLength(aMessage) - otMessageGetOffset(aMessage));
  APP_DBG(
         "%x:%x:%x:%x:%x:%x:%x:%x %d ", HostSwap16(aMessageInfo->mPeerAddr.mFields.m16[0]),
         HostSwap16(aMessageInfo->mPeerAddr.mFields.m16[1]), HostSwap16(aMessageInfo->mPeerAddr.mFields.m16[2]),
         HostSwap16(aMessageInfo->mPeerAddr.mFields.m16[3]), HostSwap16(aMessageInfo->mPeerAddr.mFields.m16[4]),
         HostSwap16(aMessageInfo->mPeerAddr.mFields.m16[5]), HostSwap16(aMessageInfo->mPeerAddr.mFields.m16[6]),
         HostSwap16(aMessageInfo->mPeerAddr.mFields.m16[7]), aMessageInfo->mPeerPort);

  length = otMessageRead(aMessage, otMessageGetOffset(aMessage), udpBuffferReceived, sizeof(udpBuffferReceived) - 1);
  udpBuffferReceived[length] = '\0';

  APP_DBG("Received %s\r\n", udpBuffferReceived);

  if(strcmp((char const*)udpBuffferReceived, (char const*)udpBufffer) == 0)
  {
    APP_DBG("Comparison OK!");
    HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_1);
  }
}
/* USER CODE END FD_LOCAL_FUNCTIONS */


stm32wbxx_it.c

/* USER CODE BEGIN PFP */
extern  void HW_IPCC_Rx_Handler(void);
extern  void HW_IPCC_Tx_Handler(void);
/* USER CODE END PFP */


/* USER CODE BEGIN 1 */
/**
  * @brief  This function handles IPCC RX occupied global interrupt request.
  * @param  None
  * @retval None
  */
void IPCC_C1_RX_IRQHandler(void)
{
  HW_IPCC_Rx_Handler();
}

/**
  * @brief  This function handles IPCC TX free global interrupt request.
  * @param  None
  * @retval None
  */
void IPCC_C1_TX_IRQHandler(void)
{
   HW_IPCC_Tx_Handler();
}
/* USER CODE END 1 */
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
STM32WB55_Nucleo是一款功能强大的开发板,用于开发基于STM32WB55微控制器的无线应用。它集成了双核Arm® Cortex®-M4和Cortex®-M0+处理器,具有丰富的外设和无线连接功能。 首先,该开发板具有丰富的外设,包括多个通用输入输出引脚、模拟输入通道、串行通信接口、定时器和PWM输出等。这些外设使得开发者可以轻松地连接其他设备和传感器,实现各种功能和应用。 其次,该开发板支持多种无线连接方式,包括Bluetooth® Low Energy (BLE)和802.15.4无线射频通信协议。这使得开发者可以开发各种无线通信应用,如物联网设备、远程控制、传感器网络等。 此外,该开发板还配备了集成的ST-LINK/V2-1调试器/程序烧录器,方便开发者进行调试和烧录。同时,它还与STM32Cube软件生态系统完全兼容,开发者可以使用STM32Cube软件包和工具来开发和调试应用程序。 对于初学者来说,该开发板提供了丰富的例程和示例代码,帮助他们快速上手。同时,开发板上的Arduino Uno连接口和Morpho连接口也为开发者提供了更多的扩展性。 总之,STM32WB55_Nucleo开发板是一款适合无线应用开发的强大工具,具有丰富的外设和无线连接功能,同时兼容STM32Cube软件生态系统,为开发者提供了便捷的开发环境。无论是初学者还是有经验的开发者,都可以通过这个开发板实现各种无线应用的开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值