본문 바로가기
전자공학/LoRa 통신

EFM32 Starter Kit를 이용하여 Simplicity Studio 사용하기(4)

by ohj921189 2020. 2. 27.
반응형

이때까지 humitemp 예제 코드를 분석하면서 필요한 기능들을 초기화, 세팅하는 부분까지 분석해보았습니다. while 문에서부터 본격적으로 센서가 어떻게 동작하고 값을 어떻게 받아오는지 프로그래밍 되어 있습니다. 아래는 humitemp 예제 코드의 전문입니다.

 

/***************************************************************************//**
 * @file
 * @brief Relative humidity and temperature sensor demo for SLSTK3400A_EFM32HG
 *******************************************************************************
 * # License
 * <b>Copyright 2018 Silicon Laboratories Inc. www.silabs.com</b>
 *******************************************************************************
 *
 * The licensor of this software is Silicon Laboratories Inc. Your use of this
 * software is governed by the terms of Silicon Labs Master Software License
 * Agreement (MSLA) available at
 * www.silabs.com/about-us/legal/master-software-license-agreement. This
 * software is distributed to you in Source Code format and is governed by the
 * sections of the MSLA applicable to Source Code.
 *
 ******************************************************************************/

#include "em_device.h"
#include "em_chip.h"
#include "em_cmu.h"
#include "em_emu.h"
#include "em_gpio.h"
#include "i2cspm.h"
#include "si7013.h"
#include "sl_sleeptimer.h"
#include "graphics.h"
#include "em_adc.h"
#include "bspconfig.h"

/***************************************************************************//**
 * Local defines
 ******************************************************************************/

/** Time (in ms) between periodic updates of the measurements. */
#define MEASUREMENT_INTERVAL_MS      2000
/** Voltage defined to indicate dead battery. */
#define LOW_BATTERY_THRESHOLD   2800

/***************************************************************************//**
 * Local variables
 ******************************************************************************/
/* Variables used by the display callback. */
static void (*mem_lcd_callback_func)(void*) = 0;
static void *mem_lcd_callback_arg = 0;

/** Flag used to indicate ADC is finished */
static volatile bool adcConversionComplete = false;

/** This flag indicates that a new measurement shall be done. */
static volatile bool measurement_flag = true;

/** Timer used for periodic update of the measurements. */
sl_sleeptimer_timer_handle_t measurement_timer;

/** Timer used for periodic maintenance of the display **/
sl_sleeptimer_timer_handle_t display_timer;

/***************************************************************************//**
 * Local prototypes
 ******************************************************************************/
static void gpioSetup(void);
static uint32_t checkBattery(void);
static void adcInit(void);
static void measure_humidity_and_temperature(I2C_TypeDef *i2c, uint32_t *rhData, int32_t *tData, uint32_t *vBat);
static void measurement_callback(sl_sleeptimer_timer_handle_t *handle, void *data);

/***************************************************************************//**
 * @brief  Main function
 ******************************************************************************/
int main(void)
{
  I2CSPM_Init_TypeDef i2cInit = I2CSPM_INIT_DEFAULT;
  uint32_t         rhData;
  bool             si7013_status;
  int32_t          tempData;
  uint32_t         vBat = 3300;
  bool             lowBatPrevious = true;
  bool             lowBat = false;

  /* Chip errata */
  CHIP_Init();

  /* Use LFXO for rtc used by the sleeptimer */
  CMU_ClockSelectSet(cmuClock_LFA, cmuSelect_LFXO);
  CMU_ClockEnable(cmuClock_HFLE, true);

  /* Initalize peripherals and drivers */
  gpioSetup();
  adcInit();
  sl_sleeptimer_init();
  GRAPHICS_Init();
  I2CSPM_Init(&i2cInit);

  /* Get initial sensor status */
  si7013_status = Si7013_Detect(i2cInit.port, SI7021_ADDR, NULL);
  GRAPHICS_ShowStatus(si7013_status, false);
  sl_sleeptimer_delay_millisecond(2000);

  /* Set up periodic measurement timer */
  sl_sleeptimer_start_periodic_timer_ms(&measurement_timer, MEASUREMENT_INTERVAL_MS, measurement_callback, NULL, 0, 0);

  EMU_EnterEM2(false);

  while (true) {
    if (measurement_flag) {
      measure_humidity_and_temperature(i2cInit.port, &rhData, &tempData, &vBat);
      measurement_flag = false;
      if (lowBatPrevious) {
        lowBat = (vBat <= LOW_BATTERY_THRESHOLD);
      } else {
        lowBat = false;
      }
      lowBatPrevious = (vBat <= LOW_BATTERY_THRESHOLD);
      GRAPHICS_Draw(tempData, rhData, lowBat);
    }
    EMU_EnterEM2(false);
  }
}

/***************************************************************************//**
 * @brief Setup GPIO interrupt for pushbuttons.
 *****************************************************************************/
static void gpioSetup(void)
{
  /* Enable GPIO clock */
  CMU_ClockEnable(cmuClock_GPIO, true);

  /* Enable si7021 sensor isolation switch */
  GPIO_PinModeSet(gpioPortC, 8, gpioModePushPull, 1);
}

/***************************************************************************//**
 * @brief This function is called whenever we want to measure the supply v.
 *        It is reponsible for starting the ADC and reading the result.
 ******************************************************************************/
static uint32_t checkBattery(void)
{
  uint32_t vData;
  /* Sample ADC */
  adcConversionComplete = false;
  ADC_Start(ADC0, adcStartSingle);
  while (!adcConversionComplete) EMU_EnterEM1();
  vData = ADC_DataSingleGet(ADC0);
  return vData;
}

/***************************************************************************//**
 * @brief ADC Interrupt handler (ADC0)
 ******************************************************************************/
void ADC0_IRQHandler(void)
{
  uint32_t flags;

  /* Clear interrupt flags */
  flags = ADC_IntGet(ADC0);
  ADC_IntClear(ADC0, flags);

  adcConversionComplete = true;
}

/***************************************************************************//**
 * @brief ADC Initialization
 ******************************************************************************/
static void adcInit(void)
{
  ADC_Init_TypeDef       init       = ADC_INIT_DEFAULT;
  ADC_InitSingle_TypeDef initSingle = ADC_INITSINGLE_DEFAULT;

  /* Enable ADC clock */
  CMU_ClockEnable(cmuClock_ADC0, true);

  /* Initiate ADC peripheral */
  ADC_Init(ADC0, &init);

  /* Setup single conversions for internal VDD/3 */
  initSingle.acqTime = adcAcqTime16;
  initSingle.input   = adcSingleInpVDDDiv3;
  ADC_InitSingle(ADC0, &initSingle);

  /* Manually set some calibration values */
  ADC0->CAL = (0x7C << _ADC_CAL_SINGLEOFFSET_SHIFT) | (0x1F << _ADC_CAL_SINGLEGAIN_SHIFT);

  /* Enable interrupt on completed conversion */
  ADC_IntEnable(ADC0, ADC_IEN_SINGLE);
  NVIC_ClearPendingIRQ(ADC0_IRQn);
  NVIC_EnableIRQ(ADC0_IRQn);
}

/***************************************************************************//**
 * @brief  Helper function to perform data measurements.
 ******************************************************************************/
static void measure_humidity_and_temperature(I2C_TypeDef *i2c, uint32_t *rhData, int32_t *tData, uint32_t *vBat)
{
  *vBat = checkBattery();
  Si7013_MeasureRHAndTemp(i2c, SI7021_ADDR, rhData, tData);
}

/***************************************************************************//**
 * @brief Callback from timer used to initiate new measurement
 ******************************************************************************/
static void measurement_callback(sl_sleeptimer_timer_handle_t *handle, void *data)
{
  (void) handle;
  (void) data;
  measurement_flag = true;
}

/***************************************************************************//**
 * @brief   The actual callback for Memory LCD toggling
 ******************************************************************************/
static void display_callback(sl_sleeptimer_timer_handle_t *handle, void *data)
{
  (void)handle;
  (void)data;
  mem_lcd_callback_func(mem_lcd_callback_arg);
}

/***************************************************************************//**
 * @brief   Register a callback function at the given frequency.
 *
 * @param[in] pFunction  Pointer to function that should be called at the
 *                       given frequency.
 * @param[in] argument   Argument to be given to the function.
 * @param[in] frequency  Frequency at which to call function at.
 *
 * @return  Always return 0
 ******************************************************************************/
int rtcIntCallbackRegister(void (*pFunction)(void*),
                           void* argument,
                           unsigned int frequency)
{
  mem_lcd_callback_func = pFunction;
  mem_lcd_callback_arg  = argument;
  uint32_t ticks = sl_sleeptimer_get_timer_frequency() / frequency;
  sl_sleeptimer_start_periodic_timer(&display_timer, ticks, display_callback, NULL, 0, 0);

  return 0;
}

 

이제 본격적으로 코드 해석을 시작하겠습니다.

 

while (true) {

 

while 문의 괄호 안이 항상 1이므로 무한 루프를 돌게 됩니다.

 

if (measurement_flag) {

 

measurement_flag가 static volatile 형식으로 지정이 되어있습니다. 값은 true로 1로 지정이 되어있습니다.

그러므로 if의 괄호 안도 1이니 if문 안으로 들어가게 됩니다. 

 

measure_humidity_and_temperature(i2cInit.port, &rhData, &tempData, &vBat);

 

이 함수는 습도와 온도를 측정하는 함수입니다. 이 함수가 어떻게 동작하는지 더 자세히 알아보기 위해 이 함수에 대해 찾아보았습니다. 

 

이 함수의 첫 번째 줄부터 차근차근 해석해보도록 하겠습니다. 우선 checkBattery라는 함수를 들어가 봅니다. 

checkBattery 함수를 들어가 보니 ADC(아날로그 디지털 컨버터) 기능을 하는 함수가 있었습니다. ADC_start는 ADC0을 사용합니다. 그러고 나서 adcConversionComplete가 0이면 EMU_EnterEM1을 실행하게 되는데 에너지 관리 모드 중 EM1은 sleep mode입니다. 그 후에 ADC0를 통해 받아온 값을 vData에 저장하고 이 값을 리턴하게 됩니다. 이 ADC 값은 vData에 저장이 되고 결과적으로는 measure_humidity_and_temperature 함수의 첫 번째 줄에 있는 변수인 vBat에 저장이 됩니다.

 

다시 돌아가서 이번에는 Si7013_MeasureRHAndTemp에 대해서 알아 보도록 합시다. 

 

이 함수의 첫 번째 줄인 Si7013_Measure에 대해서 알아봅시다. 이 함수는 크게 두 파트로 나눌 수 있습니다. 첫 번째로 아래의 사진처럼 온도센서와의 I2C 통신을 통해 온도센서에 대한 데이터를 읽어와 저장하거나 데이터를 쓰는 과정이 있습니다.

 

두 번째로는 I2C 통신이 종료되었는지 확인하는 과정이 있습니다. 종료되었으면 데이터를 data 변수에 저장하고 2를 리턴합니다. 종료되지 않았으면 data에 0을 저장하고 ret을 리턴합니다.

다시 Si7013_MeasureRHAndTemp 함수로 돌아와서 if 문부터 보게 되면 ret이 2이면 측정한 데이터를 milli-percent로 바꿉니다. 또다시 Si7013_Measure 함수가 나오는데 아까는 SI7013_READ_RH command였다면 이번에는 SI7013_READ_TEMP로 command를 바꾸어 데이터 측정을 시작합니다. 그 밑의 if 문은 ret이 2이면 측정한 데이터를 milli-degC로 바꿉니다.

 

measurement_flag = false;

 

아까는 1이었던 measurement_flag를 0으로 바꾸어 줍니다.

 

if (lowBatPrevious) {

 

lowBatPrevious는 처음 시작할 때는 1로 값이 저장이 되어있으므로 처음에는 바로 if문 안으로 들어가게 됩니다.

 

lowBat = (vBat <= LOW_BATTERY_THRESHOLD);

 

LOW_BATTERY_THRESHOLD가 2800으로 설정이 되어있습니다. 2800 아래로는 배터리 잔량이 낮은 상태입니다. vBat과 비교하여 2800보다 낮으면 lowBat이 1이 됩니다. 

 

} else {

lowBat = false;

}

 

lowBatPrevious가 1이 아니라면 lowBat에 0이 저장됩니다.

 

lowBatPrevious = (vBat <= LOW_BATTERY_THRESHOLD);

 

vBat과 비교하여 2800보다 작으면 lowBatPrevious에 1이 저장이 됩니다. 

 

GRAPHICS_Draw(tempData, rhData, lowBat);

 

이 함수에 대해 알아보도록 하겠습니다. 

 

lowBat이 1이면 "LOW BATTERY!"라고 디스플레이에 표시가 됩니다. lowBat이 0이면 측정한 온도값이 디스플레이에 표시가 됩니다. 

 

EMU_EnterEM2(false);

 

이후 deep sleep 모드로 들어갑니다. 

반응형

댓글