【STM32WLE5之lora:5、易智联LM40评估板ADC定时采集上报】


前言

本节我们在PingPong例程的基础上完成STM32WLE5内部的温度,VBAT电压及外部ADC的定时采集上报。介绍了STM32WlE5的ADC采样、软件定时和Lora私有协议发送和接收的方法,便于使用STM32WLE5构建与传统的串口Lora模块类似的点对点,点对多点的采集上报应用。
由于STM32WLE5集成的是Semtech的SX1262内核,因此在频点一致、Lora参数一致的情况下STM32WLE5能够与SX1268,SX1262实现互联互通。


一、数据上报格式

作为私有协议的Lora数据传输,为了方便管理,我们需要定义一个简单数据包格式。数据格式采用常规的TLV格式,定义如下:
在这里插入图片描述
数据由帧头、数据包长度、目标ID、本机ID、数据类型、具体数据和校验和组成。
帧头使用0xA5、0x7E;
数据包长度为整个数据包的长度,包括帧头和检验和;
远端ID为目标ID,设定0xFFFF为广播地址,集中器都可以解析;
本端ID为本机ID,标识不同的传感器;
类型部分可以定义不同的传感器或者不同数据格式,集中器根据数据类型进行相应格式的数据解析;
MCU温度为STM32WLE5内部的温度采集,数据格式为16bit q7.8;
BAT电压为LM401的输入电压,单位mV;
ADC电压为PA11引脚输入的电压,单位mV;
校验和为累加和。

发送端发送时需要指明远端ID,也可用0xFFFF作广播。
接收端匹配ID,是发给自己的或者广播地址的解析并输出,不是自己的则丢弃。
完成该例子需要两块LM401评估板。
在这里插入图片描述

二、 ADC采集

ADC采集涉及adc.c和adc_if.c两个文件。

1、ADC的初始化

adc.c为CubeMx生产的初始化文件。为了提高采集精度,我们使用过采样处理。主要修改MX_ADC_Init(),修改后的adc.c如下:

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "adc.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

ADC_HandleTypeDef hadc;

/* ADC init function */
void MX_ADC_Init(void)
{

  /* USER CODE BEGIN ADC_Init 0 */

  /* USER CODE END ADC_Init 0 */

  /* USER CODE BEGIN ADC_Init 1 */

  /* USER CODE END ADC_Init 1 */
  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  */
  hadc.Instance = ADC;
  hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc.Init.Resolution = ADC_RESOLUTION_12B;
  hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc.Init.LowPowerAutoWait = DISABLE;
  hadc.Init.LowPowerAutoPowerOff = DISABLE;
  hadc.Init.ContinuousConvMode = DISABLE;
  hadc.Init.NbrOfConversion = 1;
  hadc.Init.DiscontinuousConvMode = DISABLE;
  hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc.Init.DMAContinuousRequests = DISABLE;
  hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc.Init.SamplingTimeCommon1 = ADC_SAMPLETIME_160CYCLES_5;
  hadc.Init.SamplingTimeCommon2 = ADC_SAMPLETIME_160CYCLES_5;
 // hadc.Init.OversamplingMode = DISABLE;
 //使能过采样,16倍过采样
   hadc.Init.OversamplingMode = ENABLE;
  hadc.Init.Oversampling.Ratio = ADC_OVERSAMPLING_RATIO_16;
  hadc.Init.Oversampling.RightBitShift = ADC_RIGHTBITSHIFT_4;
  hadc.Init.Oversampling.TriggeredMode = ADC_TRIGGEREDMODE_SINGLE_TRIGGER;
  hadc.Init.TriggerFrequencyMode = ADC_TRIGGER_FREQ_HIGH;
  if (HAL_ADC_Init(&hadc) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC_Init 2 */

  /* USER CODE END ADC_Init 2 */

}


void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(adcHandle->Instance==ADC)
  {
  /* USER CODE BEGIN ADC_MspInit 0 */

  /* USER CODE END ADC_MspInit 0 */
    /* ADC clock enable */
    __HAL_RCC_ADC_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**ADC GPIO Configuration
    PA11     ------> ADC_IN7
    */
    GPIO_InitStruct.Pin = GPIO_PIN_11;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN ADC_MspInit 1 */

  /* USER CODE END ADC_MspInit 1 */
  }
}

void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{

  if(adcHandle->Instance==ADC)
  {
  /* USER CODE BEGIN ADC_MspDeInit 0 */

  /* USER CODE END ADC_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_ADC_CLK_DISABLE();

    /**ADC GPIO Configuration
    PA11     ------> ADC_IN7
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_11);

  /* USER CODE BEGIN ADC_MspDeInit 1 */

  /* USER CODE END ADC_MspDeInit 1 */
  }
}

2、ADC采集

参照ST的例程为了低功耗处理构建ADC_ReadChannels(uint32_t channel)函数,每次获取adc数据是先进行adc的初始化,再进行数据采集,最后释放adc资源。adc_if.c内容如下:


/* External variables ---------------------------------------------------------*/
/**
  * @brief ADC handle
  */
extern ADC_HandleTypeDef hadc;
/* USER CODE BEGIN EV */

/* USER CODE END EV */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
#define TEMPSENSOR_TYP_CAL1_V          (( int32_t)  760)        /*!< Internal temperature sensor, parameter V30 (unit: mV). Refer to device datasheet for min/typ/max values. */
#define TEMPSENSOR_TYP_AVGSLOPE        (( int32_t) 2500)        /*!< Internal temperature sensor, parameter Avg_Slope (unit: uV/DegCelsius). Refer to device datasheet for min/typ/max values. */

/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
/**
  * @brief This function reads the ADC channel
  * @param channel channel number to read
  * @return adc measured level value
  */
static uint32_t ADC_ReadChannels(uint32_t channel);

/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Exported functions --------------------------------------------------------*/
/* USER CODE BEGIN EF */

/* USER CODE END EF */

void SYS_InitMeasurement(void)
{
  /* USER CODE BEGIN SYS_InitMeasurement_1 */

  /* USER CODE END SYS_InitMeasurement_1 */
  hadc.Instance = ADC;
  /* USER CODE BEGIN SYS_InitMeasurement_2 */

  /* USER CODE END SYS_InitMeasurement_2 */
}

void SYS_DeInitMeasurement(void)
{
  /* USER CODE BEGIN SYS_DeInitMeasurement_1 */

  /* USER CODE END SYS_DeInitMeasurement_1 */
}

int16_t SYS_GetTemperatureLevel(void)
{
  /* USER CODE BEGIN SYS_GetTemperatureLevel_1 */

  /* USER CODE END SYS_GetTemperatureLevel_1 */
  int16_t temperatureDegreeC = 0;
  uint32_t measuredLevel = 0;
  uint16_t batteryLevelmV = SYS_GetBatteryLevel();

  measuredLevel = ADC_ReadChannels(ADC_CHANNEL_TEMPSENSOR);

  /* convert ADC level to temperature */
  /* check whether device has temperature sensor calibrated in production */
  if (((int32_t)*TEMPSENSOR_CAL2_ADDR - (int32_t)*TEMPSENSOR_CAL1_ADDR) != 0)
  {
    /* Device with temperature sensor calibrated in production:
       use device optimized parameters */
    temperatureDegreeC = __LL_ADC_CALC_TEMPERATURE(batteryLevelmV,
                                                   measuredLevel,
                                                   LL_ADC_RESOLUTION_12B);
  }
  else
  {
    /* Device with temperature sensor not calibrated in production:
       use generic parameters */
    temperatureDegreeC = __LL_ADC_CALC_TEMPERATURE_TYP_PARAMS(TEMPSENSOR_TYP_AVGSLOPE,
                                                              TEMPSENSOR_TYP_CAL1_V,
                                                              TEMPSENSOR_CAL1_TEMP,
                                                              batteryLevelmV,
                                                              measuredLevel,
                                                              LL_ADC_RESOLUTION_12B);
  }

  APP_LOG(TS_ON, VLEVEL_L, "temp= %d\n\r", temperatureDegreeC);

  /* from int16 to q8.7*/
  temperatureDegreeC <<= 8;

  return (int16_t) temperatureDegreeC;
  /* USER CODE BEGIN SYS_GetTemperatureLevel_2 */

  /* USER CODE END SYS_GetTemperatureLevel_2 */
}

uint16_t SYS_GetBatteryLevel(void)
{
  /* USER CODE BEGIN SYS_GetBatteryLevel_1 */

  /* USER CODE END SYS_GetBatteryLevel_1 */
  uint16_t batteryLevelmV = 0;
  uint32_t measuredLevel = 0;

  measuredLevel = ADC_ReadChannels(ADC_CHANNEL_VREFINT);

  if (measuredLevel == 0)
  {
    batteryLevelmV = 0;
  }
  else
  {
    if ((uint32_t)*VREFINT_CAL_ADDR != (uint32_t)0xFFFFU)
    {
      /* Device with Reference voltage calibrated in production:
         use device optimized parameters */
      batteryLevelmV = __LL_ADC_CALC_VREFANALOG_VOLTAGE(measuredLevel,
                                                        ADC_RESOLUTION_12B);
    }
    else
    {
      /* Device with Reference voltage not calibrated in production:
         use generic parameters */
      batteryLevelmV = (VREFINT_CAL_VREF * 1510) / measuredLevel;
    }
  }

  return batteryLevelmV;
  /* USER CODE BEGIN SYS_GetBatteryLevel_2 */

  /* USER CODE END SYS_GetBatteryLevel_2 */
}

/* Private Functions Definition -----------------------------------------------*/
/* USER CODE BEGIN PrFD */
uint16_t GetADC_PA11(void)
{
	return ADC_ReadChannels(ADC_CHANNEL_7);
}
/* USER CODE END PrFD */

static uint32_t ADC_ReadChannels(uint32_t channel)
{
  /* USER CODE BEGIN ADC_ReadChannels_1 */

  /* USER CODE END ADC_ReadChannels_1 */
  uint32_t ADCxConvertedValues = 0;
  ADC_ChannelConfTypeDef sConfig = {0};

  MX_ADC_Init();

  /* Start Calibration */
  if (HAL_ADCEx_Calibration_Start(&hadc) != HAL_OK)
  {
    Error_Handler();
  }

  /* Configure Regular Channel */
  sConfig.Channel = channel;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLINGTIME_COMMON_1;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  if (HAL_ADC_Start(&hadc) != HAL_OK)
  {
    /* Start Error */
    Error_Handler();
  }
  /** Wait for end of conversion */
  HAL_ADC_PollForConversion(&hadc, HAL_MAX_DELAY);

  /** Wait for end of conversion */
  HAL_ADC_Stop(&hadc) ;   /* it calls also ADC_Disable() */

  ADCxConvertedValues = HAL_ADC_GetValue(&hadc);

  HAL_ADC_DeInit(&hadc);

  return ADCxConvertedValues;
  /* USER CODE BEGIN ADC_ReadChannels_2 */

  /* USER CODE END ADC_ReadChannels_2 */
}

对应的adc_if.h如下:

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __ADC_IF_H__
#define __ADC_IF_H__

#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "adc.h"
#include "platform.h"

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */

/* USER CODE END ET */

/* Exported constants --------------------------------------------------------*/
/**
  * @brief Battery level in mV
  */
#define BAT_CR2032                  ((uint32_t) 3000)
/**
  * @brief Maximum battery level in mV
  */
#define VDD_BAT                     BAT_CR2032
/**
  * @brief Minimum battery level in mV
  */
#define VDD_MIN                     1800

/* USER CODE BEGIN EC */

/* USER CODE END EC */

/* External variables --------------------------------------------------------*/
/* USER CODE BEGIN EV */

/* USER CODE END EV */

/* Exported macro ------------------------------------------------------------*/
/* USER CODE BEGIN EM */

/* USER CODE END EM */

/* Exported functions prototypes ---------------------------------------------*/

/**
  * @brief  Initializes the ADC input
  */
void SYS_InitMeasurement(void);

/**
  * @brief DeInitializes the ADC
  */
void SYS_DeInitMeasurement(void);

/**
  * @brief  Get the current temperature
  * @return value temperature in degree Celsius( q7.8 )
  */
int16_t SYS_GetTemperatureLevel(void);

/**
  * @brief Get the current battery level
  * @return value battery level in linear scale
  */
uint16_t SYS_GetBatteryLevel(void);

uint16_t GetADC_PA11(void);
/* USER CODE BEGIN EFP */

/* USER CODE END EFP */

#ifdef __cplusplus
}
#endif

#endif /* __ADC_IF_H__ */

三、 采集端上报处理流程

初始化射频之后,启动上报定时器,定时周期为SEND_PERIOD_MS。在定时器回调中设置为S_SEND_DATA状态,并设置任务调用

State = S_SEND_DATA;
UTIL_SEQ_SetTask((1 <<CFG_SEQ_Task_SubGHz_Phy_App_Process), CFG_SEQ_Prio_0);

在任务处理的SendData_Process()中进行数据采集,并计算PA11的ADC对应电压。之后进行组包发送。修改后的subghz_phy_app.c如下:

/* 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 <stdint.h>
#include "utilities_def.h"
#include "adc_if.h"
/* USER CODE END Includes */

/* External variables ---------------------------------------------------------*/
/* USER CODE BEGIN EV */

/* USER CODE END EV */

/* Private typedef -----------------------------------------------------------*/

/* USER CODE BEGIN PTD */
typedef enum
{
  S_IDLE,
  S_RX,
  S_RX_TIMEOUT,
  S_RX_ERROR,
  S_TX,
  S_TXing,
  S_TX_TIMEOUT,
  S_SEND_DATA,
} States_t;
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* Configurations */
/*Timeout*/
#define RX_TIMEOUT_VALUE              5000
#define TX_TIMEOUT_VALUE              3000
//上报周期
#define SEND_PERIOD_MS              	10000
//本端ID
#define LOCAL_ID						0x01
//远端ID
#define REMOTE_ID						0x10
//数据类型
#define DATA_TYPE						0x01
/*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) */


static RadioEvents_t RadioEvents;
/* USER CODE BEGIN PV */

/*Ping Pong FSM states */
static States_t State = S_IDLE;
/* 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 timerSendData;
/* 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;
//采集数据
static int16_t temp_v=0;
static uint16_t bat_v=0;
static uint16_t pa11_v=0;


/* 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 OnSendDataEvent(void *context);

/**
  * @brief PingPong state machine implementation
  */
static void SendData_Process(void);
static uint16_t PackData(uint16_t r_id);
static uint8_t SumCheck(uint8_t *buf,uint8_t len);
/* 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));

  /*创建定时器对象*/
  UTIL_TIMER_Create(&timerSendData, 0xFFFFFFFFU, UTIL_TIMER_ONESHOT, OnSendDataEvent, NULL);
/*设置定时周期*/
  UTIL_TIMER_SetPeriod(&timerSendData, SEND_PERIOD_MS);
/*启动定时*/	
  UTIL_TIMER_Start(&timerSendData);
  /* 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);

  /*设置射频频率*/
  Radio.SetChannel(RF_FREQUENCY);

  /* Radio configuration */
  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);

  /* LED initialization*/
  BSP_LED_Init(LED_GREEN);
  BSP_LED_Init(LED_RED);
  BSP_LED_Init(LED_BLUE);
  /* 按键 initialization*/
  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, SendData_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 = S_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");
  APP_LOG(TS_ON, VLEVEL_L, "RssiValue=%d dBm, SnrValue=%ddB\n\r", rssi, LoraSnr_FskCfo);
	
  /* Update the State of the FSM*/
  State = S_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 = S_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 = S_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 = S_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 SendData_Process(void)
{

	uint16_t temp = 0;
	uint16_t sendlen = 0;
//射频休眠	
  switch (State)
  {
	 case S_IDLE:
	 case S_TXing:	 
		break;  
    case S_RX:
		Radio.Sleep();
		State = S_IDLE;
      break;
    case S_TX:
		BSP_LED_Off(LED_GREEN);
		BSP_LED_Off(LED_RED);
		 Radio.Sleep();
		State = S_IDLE;
      break;
    case S_RX_TIMEOUT:
    case S_RX_ERROR:
    case S_TX_TIMEOUT:
		 Radio.Sleep();
		State = S_IDLE;
      break;
	case S_SEND_DATA:
		
		temp_v = SYS_GetTemperatureLevel();
		bat_v = SYS_GetBatteryLevel();
		temp = GetADC_PA11();
		//将PA11的ADC转换成电压,单位mV
		pa11_v =__LL_ADC_CALC_DATA_TO_VOLTAGE(bat_v, temp,ADC_RESOLUTION_12B);
		APP_LOG(TS_ON, VLEVEL_L, "TemperatureLevel = %d.%d  BatteryLevel = %d PA11Level = %d\n\r",temp_v>>8,(uint8_t)temp_v,bat_v,pa11_v);
		//组包
		sendlen = PackData(REMOTE_ID);
		if(sendlen < MAX_APP_BUFFER_SIZE)
			Radio.Send(BufferTx,sendlen);
		else
			APP_PRINTF("The length of the sent data is greater than MAX_APP_BUFFER_SIZE(%d)\n\r",MAX_APP_BUFFER_SIZE);
		State = S_TXing;
      break;
    default:
      break;
  }
}

static void OnSendDataEvent(void *context)
{
  BSP_LED_On(LED_GREEN);
  BSP_LED_On(LED_RED);
  UTIL_TIMER_Start(&timerSendData);
	//空闲状态时进行数据发送
  if(State == S_IDLE)
  {
	  State = S_SEND_DATA;
	  UTIL_SEQ_SetTask((1 << CFG_SEQ_Task_SubGHz_Phy_App_Process), CFG_SEQ_Prio_0);
  }	  
  else
  {
	  APP_PRINTF("OnSendDataEvent Error State(%d)\n\r",State);
  }
}

static uint16_t PackData(uint16_t r_id)
{
	uint8_t len = 0;
	uint8_t i = 0;
	uint16_t id = LOCAL_ID;
	BufferTx[len++]= 0xA5;
	BufferTx[len++]= 0x7E;
	len++;
	memcpy(&BufferTx[len],&r_id,2);
	len+=2;
	memcpy(&BufferTx[len],&id,2);
	len+=2;
	BufferTx[len++]= DATA_TYPE;
	memcpy(&BufferTx[len],&temp_v,2);
	len+=2;
	memcpy(&BufferTx[len],&bat_v,2);
	len+=2;
	memcpy(&BufferTx[len],&pa11_v,2);
	len+=2;
	//长度
	BufferTx[2]= len+1;
	BufferTx[len]= SumCheck(BufferTx,len);
	len++;
	
	APP_LOG(TS_ON, VLEVEL_L, "%s: payload. size=%d \n\r", __func__,len);
	  for (int i = 0; i < len; i++)
	  {
		APP_LOG(TS_OFF, VLEVEL_L, "%02X", BufferTx[i]);
		if (i % 16 == 15)
		{
		  APP_LOG(TS_OFF, VLEVEL_L, "\n\r");
		}
	  }
	APP_LOG(TS_OFF, VLEVEL_L, "\n\r");
	return len;
}

static uint8_t SumCheck(uint8_t *buf,uint8_t len)
{
	uint8_t i = 0;
	uint8_t sum = 0;
	for(i = 0;i < len;i++)
	{
		sum += buf[i];
	}
	return sum;
}

四、 接收端接收处理流程

接收端主要对接收到的数据进行帧头、长度,ID以及校验和进行比对,都没有问题的情况下进行解析并答应输出。修改后的subghz_phy_app.c如下:


/* 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
{
  S_IDLE,	
  S_RX,
  S_RX_TIMEOUT,
  S_RX_ERROR,
  S_TX,
  S_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
#define SEND_PERIOD_MS              	10000
#define LOCAL_ID						0x01
#define Concentrator_ID						0x10
#define DATA_TYPE01						0x01

/*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) */

/* 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 = S_IDLE;
/* 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;
/* 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
  */

/**
  * @brief PingPong state machine implementation
  */
static void Concentrator_Process(void);
static uint8_t SumCheck(uint8_t *buf,uint8_t len);
/* 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));


  /* 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 */
  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);

  /* 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(0);
  /*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, Concentrator_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 = S_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");
  APP_LOG(TS_ON, VLEVEL_L, "RssiValue=%d dBm, SnrValue=%ddB\n\r", rssi, LoraSnr_FskCfo);
	BSP_LED_On(LED_GREEN);
  BSP_LED_On(LED_RED);
  /* Record payload Signal to noise ratio in Lora*/
  SnrValue = LoraSnr_FskCfo;
  /* Update the State of the FSM*/
  State = S_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 < RxBufferSize; 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 = S_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 = S_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 = S_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 Concentrator_Process(void)
{
	uint16_t SenID = 0;
	uint16_t ConID = 0;
	int16_t temp_v = 0;
	uint16_t bat_v = 0;
	uint16_t pa11_v = 0;
	Radio.Rx(0);
  switch (State)
  {
	  //接收处理
    case S_RX:
		 APP_LOG(TS_ON, VLEVEL_L, "payload. size=%d \n\r", RxBufferSize);
	
	for (int i = 0; i < RxBufferSize; i++)
		{
			APP_LOG(TS_OFF, VLEVEL_L, "%02X", BufferRx[i]);
			if (i % 16 == 15)
			{
			APP_LOG(TS_OFF, VLEVEL_L, "\n\r");
			}
		}
		APP_LOG(TS_OFF, VLEVEL_L, "\n\r");
	
	//判断长度
	 if(BufferRx[2] == RxBufferSize)
	 {
		//判断帧头
		if((BufferRx[0] == 0xA5) && (BufferRx[1] == 0x7E))	
		{
			//判断SUM
			if(BufferRx[RxBufferSize - 1] == SumCheck(BufferRx,RxBufferSize - 1))
			{
				//判断地址是否正确
				memcpy(&ConID,&BufferRx[3],2);
				if((ConID == Concentrator_ID) || (ConID == 0xFFFF))
				{
					//传感器类型
					if(BufferRx[7] == DATA_TYPE01)
					{
						//获取传感器ID
						memcpy(&SenID,&BufferRx[5],2);
						//获取温度
						memcpy(&temp_v,&BufferRx[8],2);
						//获取BAT电压
						memcpy(&bat_v,&BufferRx[10],2);
						//获取PA11的ADC
						memcpy(&pa11_v,&BufferRx[12],2);
						APP_LOG(TS_ON, VLEVEL_L, "Recv:ID=0x%04x Temperature = %d.%d  Battery = %d PA11Value = %d\n\r",SenID,temp_v>>8,(uint8_t)temp_v,bat_v,pa11_v);
					}
					/*
					//其他类型的解析
					else if
					{
					}
					*/
					else
					{
						APP_LOG(TS_OFF, VLEVEL_M, "Unknown type\n\r");
					}
					
				}
				else
				{
					APP_LOG(TS_OFF, VLEVEL_M, "The desired ID =%d ,ID = %d error\n\r",Concentrator_ID,ConID);
				}
			}
			else
			{
				APP_LOG(TS_OFF, VLEVEL_M, "Checksum error\n\r");
			}
		  
			
		}
		else
		{
			APP_LOG(TS_OFF, VLEVEL_M, "The data header is incorrect\n\r");
		}
	 }
	 else
	 {
		 APP_LOG(TS_OFF, VLEVEL_M, "Packet length error\n\r");
	 }
	 BSP_LED_Off(LED_GREEN);
	BSP_LED_Off(LED_RED);
     State = S_IDLE;
      break;
	case S_IDLE: 
    case S_TX:
    case S_RX_TIMEOUT:
    case S_RX_ERROR:
    case S_TX_TIMEOUT:
		State = S_IDLE;
      break;
    default:
      break;
  }
}
static uint8_t SumCheck(uint8_t *buf,uint8_t len)
{
	uint8_t i = 0;
	uint8_t sum = 0;
	for(i = 0;i < len;i++)
	{
		sum += buf[i];
	}
	return sum;
}

将PA11首先接电源,然后接地。执行效果如下:
左边为采集端右边为接收端。
在这里插入图片描述

五、 小结

通过这个ADC定时采集并过LoRa进行上报的例子,便于理解基于STM32WLE5的传感器开发过程。实际应用情况比这复杂的多,还需考虑传感器较多时碰撞规避,集中器对传感器的数据下发或者配置等等。当然也包括稳健性的考量需要启动看门狗,做一些异常处理等等。


文件中提供的易智联Demo板链接如下:

https://item.taobao.com/item.htm?spm=a1z0k.7628869.0.0.4fbb1be2qSrsJg&id=655801203935&_u=t2dmg8j26111

版权声明:本文为CSDN博主「ww2801」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ww2801/article/details/122583995

生成海报
点赞 0

ww2801

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

ESP32S2+ES8388移植过程及问题

电路图如下, 有点小瑕疵ES8388_VMID PIN10/19/20电容没有忘加,查资料应该不影响语言输出,可能噪音大,如果能导致不输出请告诉我一下。 ESP32S2管脚映射 这里主

基于STM32的LoRa无线通信(AS32—TTL-1W)

目录 无线串口简介 项目简介 发送端代码 接收端代码 项目总结 前些天接触到一个小项目,需要使用无线传输的功能,不仅如此还需要远距离的通信,搜索资料后最终选择了泽耀科技的LoRa&#xff08