在PingPong例程的基础上完成内部MCU温度,VBAT定压以及外部ADC的定时采集上报。
了解STM32WLE5的ADC采样、软件定时和LoRa私有发送和接收的方法。便于使用STM32WLE5构建与传统的串口LoRa模块类似的点对点,点对多点的采集上报应用。
由于STM32WLE5集成的是Semtech的SX1262内核,因此在频点一致、LoRa参数一致的情况下STM32WLE5能够与SX1268、SX1262完全兼容,实现互联互通。
数据上报格式
数据格式采样常规的TLV格式,定义如下:
发送端发送时需要指明远端ID,一般0xFFFF作为广播地址。
接收端匹配ID,是发给自己的或者广播地址的解析并输出,不是自己的则丢弃。
ADC采集
ADC采集设计adc.c和adc_if.c两个文件。
ADC的初始化
adc.c为CubeMX生产的初始化文件。为了提高采集精度,我们使用过采样处理。修改后的初始化参数如此:
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;
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 */
}
ADC采集
参照ST的例程为了低功耗处理构建ADC_ReadChannels(uint32_t channel),每次获取adc数据是先进行adc的初始化,在进行数据采集,最后释放adc资源。adc_if.c内容如下:
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "adc_if.h"
#include "sys_app.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* 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__ */
PingPong例程中已经提供了完整的低功耗处理框架,用户只需要打开应用低功耗定义即可进行测试。进行扩展时,用户只需在自己开发的功能模块,增加低功耗处理即可完成整个工程的低功耗处理,大大降低用户的开发难度。
低功耗模式选择
STM32WLE5具有Run、Sleep、LPRun、LPSleep、Stop0、Stop1、Stop2、Standby、Shutdown这些丰富的工作模式可供选择。
低功耗模式选择
电池供电传感器一般都是定时上报工作模式,因此我们需要一个定时器;再者,我们希望唤醒后能够保持原来的参数,继续执行原来的程序。基于这两点选择了RTC+STOP2模式。
RTC+STOP2模式的典型电流1uA,也符合大部分应用场景的预期。
低功耗处理简介
低功耗处理集中实现了各个独立模块的低功耗处理需求,并在系统进入空闲模式时管理这些低功耗任务。
例如,当UART2的DMA用于向控制台打印数据时,系统不得进入低于睡眠模式的低功耗模式,因为DMA时钟在停止模式下关闭。
每个需要低功耗管理的模块需要在utilitie_def.h中定义一个ID,最多可以定义32个ID。如下所示:
typedef enum
{
CFG_LPM_APPLi_id,
CFG_LPM_UART_TX_id,
}CFG_LPM_Id_t;
低功耗处理
比如UART2的输出ID为CFG_LPM_UART_TX_id。
每次调用打印输出是调用UTIL_ADV_TRACE_PreSendHook()禁止MCU进入STOP模式,在DMA处理完成后,并且判断队列里没有数据继续输出时调用UTIL_ADC_TRACE_PoetSendHook()允许MCU进入STOP模式。
当所有ID都允许MCU进入低功耗时,MCU才会进入低功耗模式。
在序列调度的空闲时真正的执行进入低功耗工作。
void UTIL_SEQ_Idle(void)
{
UTIL_LPM_EnterLowPower();
}
UTIL_LPM_EnterLowPower()的实现位于stm32_lpm.c,具体的执行动作UTIL_PowerDriver.EnterXXXMode的实现位于stm32_lpm_if.c。
void UTIL_LPM_EnterLowPower(void)
{
UTIL_LPM_ENTER_CRITICAL_SECTION_ELP( );
if( StopModeDisable != UTIL_LPM_NO_BIT_SET )
{
/**
* At least one user disallows Stop Mode
* SLEEP mode is required
*/
UTIL_PowerDriver.EnterSleepMode( );
UTIL_PowerDriver.ExitSleepMode( );
}
else
{
if( OffModeDisable != UTIL_LPM_NO_BIT_SET )
{
/**
* At least one user disallows Off Mode
* STOP mode is required
*/
UTIL_PowerDriver.EnterStopMode( );
UTIL_PowerDriver.ExitStopMode( );
}
else
{
/**
* OFF mode is required
*/
UTIL_PowerDriver.EnterOffMode( );
UTIL_PowerDriver.ExitOffMode( );
}
}
UTIL_LPM_EXIT_CRITICAL_SECTION_ELP( );
}
打开低功耗使能
系统应用的低功耗控制ID为CFG_LPM_APPLI_id,在SystemApp_Init(void)中进行配置。如下:
UTIL_LPM_SetOffMode((1 << CFG_LPM_APPLI_Id),UTIL_LPM_DISABLE);
#if defined (LOW_POWER_DISABLE) && (LOW_POWER_DISABLE == 1)
/* Disable Stop Mode */
UTIL_LPM_SetStopMode((1 << CFG_LPM_APPLI_Id), UTIL_LPM_DISABLE);
#elif !defined (LOW_POWER_DISABLE)
#error LOW_POWER_DISABLE not defined
#endif /* LOW_POWER_DISABLE */
宏LOW_POWER_DISABLE宏定义于sys_conf.h中,将其修改为“0”
/**
* @brief Disable Low Power mode
* @note 0: LowPowerMode enabled. MCU enters stop2 mode, 1: LowPowerMode disabled. MCU enters sleep mode only
*/
#define LOW_POWER_DISABLE 0