Using Silicon Labs LTE-M Expansion Board with the EMF32GG11 Giant Gecko

Created by Ben Roloff, last modified on Mar 29, 2019

Introduction

IoT is a popular buzzword for wireless electronics. It covers a lot of different wireless protocols. Which protocol you use depends on the needs of your project. Range, price, regulations are all important factors. One protocol that is being used more is cellular. The advantage of cellular is range. You don’t need to be near a gateway or within line of sight, you just need to be in a cellular network area. With the creation of LTE-M, which is designed for more IoT type applications, the price for data is much more manageable. Silicon Labs is adding cellular capabilities to their wireless portfolio. They have added a cellular expansion board for their microcontrollers. On the board they make use of Digi International’s LTE-M module, for ease of use.

Bill of Materials

Project

We are going to have the Hall Effect Sensor on the Gecko board set to send an interrupt anytime the magnetic field changes polarity. The Gecko will then send a HTTP POST request to an IFTTT webhook that will send a notification to your phone.

Setting Up IFTTT

IFTTT stands for IF This Then That. It is an easy to use app for dealing with IoT applications. You can setup an IFTTT account for free. You then just have to enable their webhook service. Then if you download the app on your smartphone, inside there you can create an ‘applet’. The applet can be named anything just make the event a webhook and the action a notification.

The SIM Card

The Cellular expansion kit comes with a Hologram SIM card. You can go through them and use their system. You get the benefits of hologram.io which allows you to send SMS messages to the card and to read messages heading out. You can also buy a SIM card through us. We have plans with AT&T and Verizon along with some others. The board will work with any SIM card that is able to use AT&T or Verizons frequencies. There are just some changes you must make to the code if you are not using an AT&T card. The first is the APN, it is different for every carrier. Our FAQ has the APN for the carriers we have. The second is that you have to tell the Xbee if it will be on AT&T, Verizon, or both carrier band frequencies.

LTE-M vs LTE CAT1

This expansion board uses LTE-M. It is a network designed more for smaller IoT applications that only needs to send small packets of data everyone once and awhile. It is not meant for heavy traffic or phone calls. This in turn gives the user cheaper rates for coverage. As of right now SMS does not work over LTE-M, if you are looking to use SMS messaging you should use a LTE Cat 1 module. LTE Cat 1 is similar to what your phone uses, Cat 4 or higher, but it has a smaller data throughput. It is a good in-between for LTE-M and a high-speed LTE module.

The Code

Includes, Drivers, and Libraries

The code uses the Gecko SDK with the Xbee third party library. After starting a new project, adding the necessary include files and libraries is an easy process. First you will need to add some Defined symbols in the Compiler and Assembler. You can right click your project and go to its properties. Under the settings in C/C++ Build you can find the symbols. You will need to add the symbols to both the GNU ARM C Compiler and the GNU ARM Assembler. You will be adding all the Xbee symbols. If you are not using an AT&T SIM card your APN will be different, make sure you put your SIM card carrier’s APN in the XBEE_TARGET_APN.

You will also need to add the necessary include files. You can add them under C/C++ General with Paths and Symbols. Just click Add… , make sure to check add to all configurations, and browse to the necessary folders. If you know the paths you can just type them in, Using ${StudioSdkPath} at the beginning will shorten the amount you need to type. If you want to use the display on the SLSTK3701A you will need a displayconfigapp.h. This contains the necessary configurations for the display. You can find the file in most of the example programs and just copy one and make any changes you may want. You will need it for this example since the display is used. You must also add its path to the includes. The paths to include are:

Includes

  • “${StudioSdkPath}/util/third_party/digi_lte/xbee”
  • “${StudioSdkPath}/util/third_party/digi_lte/xbee/config/inc”
  • “${StudioSdkPath}/util/third_party/digi_lte/xbee/inc/xbee”
  • “${StudioSdkPath}/util/third_party/digi_lte/xbee/inc”
  • “${StudioSdkPath}/util/third_party/digi_lte/xbee/src/efm32”
  • “${StudioSdkPath}/platform/middleware/glib”
  • “${StudioSdkPath}/platform/middleware/glib/dmd”
  • “${StudioSdkPath}/platform/middleware/glib/glib”
  • “${StudioSdkPath}/platform/middleware/glib/dmd/display”
  • “${StudioSdkPath}/platform/middleware/glib/dmd/ssd2119”

With the necessary include files we will need to add a few drivers, emlib libraries, and Xbee library files. You can add folders to hold them. To add the files right click on the folder you want to add them in and click Import. Choose the More Import Options at the bottom. And click File System. Find the necessary files and add them. The files should be in the same folders as the include files. To make sure you have the necessary includes and files build your project and if any errors pop up look at what they say are missing. The files to include are:

Xbee

  • hexdump.c
  • hexstrtobyte.c
  • jslong.c
  • memcheck.c
  • swapbytes.c
  • swapcpy.c
  • wpan_types.c
  • xbee_atcmd.c
  • xbee_atmode.c
  • xbee_cbuf.c
  • xbee_config.c
  • xbee_device.c
  • xbee_ipv4.c
  • xbee_platform_efm32.c
  • xbee_serial_efm32.c
  • xmodem_crc16.c

Emlib

  • em_assert.c
  • em_cmu.c
  • em_core.c
  • em_emu.c
  • em_gpio.c
  • em_i2c.c
  • em_prs.c
  • em_rtcc.c
  • em_usart.c

Drivers

  • display.c
  • displayls013b7dh06.c
  • displaypalemlib.c
  • i2cspm.c
  • retargetio.c
  • retargettextdisplay.c
  • si72xx.c
  • textdisplayc
  • udelay.c


Main.c

When you have all the necessary includes, drivers, and libraries, then you just need to write the main file. A lot of the functions and processes are taken from Silicon labs examples, because they show the proper way to setup the Xbee correctly so that it works. The setup and connection usually takes a little while. A few minutes at first. It can take longer if you are in a building or in an area with poor cell reception. The data upload and download is not fast, but it is not meant to be. The code itself is simple you start by initializing everything. And then you go into an infinite loop, waiting for an interrupt. The hall effect sensor is set to latch mode which will set pin PB0 to high when it senses a change in polarity. This trigger an interrupt that changes the door flag and the initial change flag. This puts it into either door opened or closed. If it is the initial change it will send a notification to your phone. It does this by sending a HTTP POST command over IPv4. The general syntax of a post command doesn’t change, but depending on where you are sending it, what you all need will be different. I have replaced the event name and key in the string with X’s you will need to put in yours for the code to work. For this I am sending it to a webhook on IFTTT. Once the webhook is called IFTTT sends the value I sent to a notification on my phone. Sending the POST command is a great way to upload data to the cloud. It doesn’t take much and can be sent from anywhere. Using HTTP commands and notifications also avoids extra charges for using SMS, if your SIM card has that capability. The code also will display information on the display. It will keep you updated on the progress of the initialization of the Xbee and cellular connection. It also will display the door status for troubleshooting.

main.c

/*
 *  This project takes an interrupt from the Hall Effect sensor and
 *  sends that info over IPv4 to IFTTT to push a notification to a
 *  phone.
 *
*/
 
// includes
#include "em_device.h"
#include "em_chip.h"
#include "em_emu.h"
#include "em_cmu.h"
#include "em_i2c.h"
#include "bsp.h"
#include "display.h"
#include "textdisplay.h"
#include "retargettextdisplay.h"
#include "si72xx.h"
#include "si7210_config.h"
#include "i2cspm.h"
 
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
 
#include "xbee/platform.h"
#include "xbee/device.h"
#include "xbee/atcmd.h"
#include "xbee_config.h"
#include "xbee/ipv4.h"
#include "xbee/byteorder.h"
#include "xbee/tx_status.h"
 
#define XBEE_TARGET_BAUD 115200
#define IFTTT_SERVER_URL     "maker.ifttt.com"
#define IFTTT_PROTOCOL_PORT  443
#define HTTP_BUFFER_LEN  512
 
static char sendBuff[HTTP_BUFFER_LEN];
xbee_serial_t ser1;
xbee_dev_t myXbee;
xbee_ipv4_envelope_t env;
uint32_t serverAddrBE = 0;  /* The IPV4 address of the server */
static int failures = 0;
static int txStat = 0xFF;
bool door = false;
bool Justsent = false;
 
/*
 *  States of the HTTP send
 */
static struct HttpSendContext {
  enum {
    HTTP_SEND_NONE,   /* No data sent yet */
    HTTP_SEND_WAIT,   /* Data has been sent, waiting for confirmation */
    HTTP_SEND_RESEND, /* We did not receive confirmation, resend */
    HTTP_SEND_DONE,   /* Confirmation received, send complete */
    HTTP_SEND_ERROR
  } state;
  uint8_t frame_id;
} sendContext;
 
 
int statusHandler(xbee_dev_t *xbee,
                  const void FAR *raw,
                  uint16_t length,
                  void FAR *context);
 
#define FRAME_HANDLE_TX_STATUS \
  { XBEE_FRAME_TX_STATUS, 0, statusHandler, NULL }
 
/* Table used to dispatch received frames */
const xbee_dispatch_table_entry_t xbee_frame_handlers[] =
{
  XBEE_FRAME_HANDLE_LOCAL_AT,
  FRAME_HANDLE_TX_STATUS,
  XBEE_FRAME_TABLE_END
};
 
/*
 *  Status Handler for the state of the HTTP communication
 */
int statusHandler(xbee_dev_t *xbee,
                  const void FAR *raw,
                  uint16_t length,
                  void FAR *context)
{
  (void) xbee;
  (void) length;
  (void) context;
 
  const xbee_frame_tx_status_t *status = raw;
  /* If we are waiting for a status */
  if (sendContext.state == HTTP_SEND_WAIT) {
    /* If this is the status we are looking for */
    if (sendContext.frame_id == status->frame_id) {
      txStat = status->delivery;
      if (status->delivery == XBEE_TX_DELIVERY_SUCCESS) {
        /* If the send was successful, we are done */
        sendContext.state = HTTP_SEND_DONE;
      } else {
        /* If the send was not successful, resend that packet */
        sendContext.state = HTTP_SEND_RESEND;
      }
    }
  }
  return 0;
}
 
/*
 *  Sets up the GPIO pins for the enable on the Hall Effect Sensor and its interrupt pin
 */
static void gpioSetup(void)
{
  // Enable the CMU clock
  CMU_ClockEnable(cmuClock_HFPER, true);
  CMU_ClockEnable(cmuClock_GPIO, true);
 
  // Enable power to the si7210 and its interrupt pin
  GPIO_PinModeSet(gpioPortB, 3, gpioModePushPull, 1);
  GPIO_PinModeSet(gpioPortB, 0, gpioModeInputPull, 1);
}
 
static I2CSPM_Init_TypeDef i2cInit = {
  I2C2,                     // Use I2C instance 2
  gpioPortI,                // SCL port
  5,                        // SCL pin
  gpioPortI,                // SDA port
  4,                        // SDA pin
  7,                        // Location of SCL
  7,                        // Location of SDA
  0,                        // Use currently configured reference clock
  I2C_FREQ_STANDARD_MAX,    // Set to standard rate
  i2cClockHLRStandard,      // Set to use 4:4 low/high duty cycle
};
 
/*
 * Initializes the Hall Effect Sensor and its intterrupt
 */
void HallInit(void)
{
    // Set the Hall Effect Sensor into latch mode
    Si72xx_EnterLatchMode(SI7210_CONFIG_I2C_DEVICE,(SI7210_CONFIG_I2C_BUS_ADDRESS << 1));
    // Disable any GPIO interrupts
    GPIO_IntDisable(0);
    // Enable the interrupt at B0 to trigger on rising edge
    GPIO_ExtIntConfig(gpioPortB,0,0,true,false,true);
    // Enable the interrupt in NVIC
    NVIC_EnableIRQ(GPIO_EVEN_IRQn);
}
 
void delay(uint32_t dlyTicks)
{
  uint32_t curTicks;
 
  curTicks = xbee_millisecond_timer();
  while ((xbee_millisecond_timer() - curTicks) < dlyTicks) ;
}
 
void printDeviceDescription(void)
{
  printf("\fHardware Version:\n%x\nFirmware Version:\n%x\nBaud Rate:\n%u\n",
         (unsigned int) myXbee.hardware_version,
         (unsigned int) myXbee.firmware_version,
         (unsigned int) myXbee.serport.baudrate);
}
 
 
int xbeeConnectCallback(const xbee_cmd_response_t FAR *response)
{
  if (((response->flags & XBEE_CMD_RESP_MASK_STATUS) == XBEE_AT_RESP_SUCCESS)
      && response->value_length == 1) {
    if (response->value_bytes[0] == 0) {
      myXbee.wpan_dev.flags |= WPAN_FLAG_JOINED;
    } else {
      myXbee.wpan_dev.flags &= ~(WPAN_FLAG_JOINED);
    }
  }
  return 0;
}
 
/***************************************************************************//**
 * @brief Callback for LA command for resolving a fully qualified domain name
 ******************************************************************************/
int xbeeFqdnCallback(const xbee_cmd_response_t FAR *response)
{
  if (((response->flags & XBEE_CMD_RESP_MASK_STATUS) == XBEE_AT_RESP_SUCCESS)
      && response->value_length == 4) { /* Only copy if its a valid response */
    /* Copy the big-endian address */
    memcpy(&serverAddrBE, response->value_bytes, 4);
  }
  return 0;
}
 
void sendAtRequest(const char *command,
                   const char *parameters,
                   xbee_cmd_callback_fn callback,
                   void FAR *context)
{
  int16_t request;
 
  request = xbee_cmd_create(&myXbee, command);
 
  if (request > 0) {
    xbee_cmd_set_callback(request, callback, context);
    if (parameters != NULL) {
      xbee_cmd_set_param_bytes(request, parameters, strlen(parameters));
    }
    xbee_cmd_send(request);
    xbee_cmd_request_t *thisReq = _xbee_cmd_handle_to_address(request);
    /* Checks if the command has responded or timed-out */
    while (thisReq->device != NULL) {
      xbee_dev_tick(&myXbee);
      xbee_cmd_tick();
      delay(100);
    }
  }
}
 
/*
 *  Function to make sure your Xbee is connected to the network
 */
void checkConnection(void)
{
  while (!(WPAN_DEV_IS_JOINED(&(myXbee.wpan_dev)))) {
    sendAtRequest("AI", NULL, xbeeConnectCallback, NULL);
    printf(".");
    /* Wait for XBee to respond */
    delay(100);
  }
}
 
/***************************************************************************//**
 * @brief Blocks, Sends request to the XBee using LA command to resolve a FQDN
 ******************************************************************************/
void resolveIFTTTServerFqdn(void)
{
  while (serverAddrBE == 0) {
    sendAtRequest("LA", IFTTT_SERVER_URL, xbeeFqdnCallback, NULL);
    /* Print a dot for every attempt */
    printf(".");
    /* Wait for XBee to respond */
    delay(100);
  }
}
 
/*
 *  This sends the actual HTTP command.  It will send it till it has a success, timeout, or error
 */
int httpClientSend(xbee_ipv4_envelope_t *env,
                   const char *buf,
                   size_t bufLen,
                   int options,
                   int timeout)
{
  int ret, temp;
 
  env->length = bufLen;
  env->payload = buf;
  env->options = options;
  sendContext.state = HTTP_SEND_WAIT;
  txStat = 0xFF;
 
  /* Store the next frame ID in context (without incrementing it) */
  temp = env->xbee->frame_id;
  sendContext.frame_id = xbee_next_frame_id(env->xbee);
  env->xbee->frame_id = temp;
 
  ret = xbee_ipv4_envelope_send(env);
 
  while (sendContext.state != HTTP_SEND_DONE) {
    while (sendContext.state == HTTP_SEND_WAIT) {
      if (ret != 0) {
        sendContext.state = HTTP_SEND_ERROR;
        ++failures;
        return ret;
      }
      xbee_dev_tick(env->xbee);
 
      if (xbee_seconds_timer() >= (uint32_t) timeout && timeout >= 0) {
        sendContext.state = HTTP_SEND_ERROR;
        return -ETIMEDOUT;
      }
    }
    if (sendContext.state == HTTP_SEND_DONE) {
      failures = 0;
      break;
    }
    if (txStat == 0x32) {
      sendContext.state = HTTP_SEND_ERROR;
      ++failures;
      return -EBUSY;
    }
    sendContext.state = HTTP_SEND_WAIT;
 
    /* Store the next frame ID in context (without incrementing it) */
    temp = env->xbee->frame_id;
    sendContext.frame_id = xbee_next_frame_id(env->xbee);
    env->xbee->frame_id = temp;
 
    ret = xbee_ipv4_envelope_send(env);
  }
  return 0;
}
 
/*
 *  This creates the envelope for HTTP command
 */
int sendDoorStatus(char* data)
{
  env.xbee = &myXbee; /* The device to send it on */
  env.remote_addr_be = serverAddrBE; /* Big endian IPV4 address */
  env.local_port = 0; /* For a new socket connection, local port must be 0 */
  env.remote_port = IFTTT_PROTOCOL_PORT; /* The destination port */
  env.protocol = XBEE_IPV4_PROTOCOL_SSL; /* SSL protocol */
  // This creates the POST command string. The event name and key have been X'd out
  snprintf(sendBuff, HTTP_BUFFER_LEN, "POST /trigger/XXXXX/with/key/XXXXXXXXXXXXXXX HTTP/1.1\r\n"
          "HOST: maker.ifttt.com:443\r\nContent-Type: application/json\r\nContent-Length: 19"
          "\r\n\n{\"value1\":\"%s\"}\r\n", data);
  // Send the envelope and return its result
  return httpClientSend(&env,sendBuff,strlen(sendBuff),0,xbee_seconds_timer()+10); /* Finally send the packet */
}
 
void initCommandLayer(void)
{
  xbee_cmd_init_device(&myXbee);
  /* Wait for XBee to respond */
  while (!(xbee_cmd_query_status(&myXbee) == XBEE_COMMAND_LIST_DONE
           || xbee_cmd_query_status(&myXbee) == XBEE_COMMAND_LIST_TIMEOUT)) {
    xbee_dev_tick(&myXbee);
    printf(".");
    delay(100);
  }
}
 
/*
 *  This initializes the Xbee for an AT&T SIM card and sets it to the correct APN
 */
int Xbee_init(void)
{
    xbee_serial_t ser1;
    ser1.baudrate = XBEE_TARGET_BAUD;
    // Initialize the XBee device (identifies device)
    xbee_dev_init(&myXbee, &ser1, NULL, NULL);
 
      /* Place the XBee into API mode */
      if (configureXBee(&myXbee,
                        XBEE_TARGET_BAUD,
                        XBEE_CONFIG_MODE_API,
                        XBEE_CONFIG_CARRIER_ATT,
                        TRUE)) {
        return -EIO;
      }
      /* Reinitialize with correct settings */
      myXbee.flags &= ~XBEE_DEV_FLAG_CMD_INIT;
      myXbee.flags &= ~XBEE_DEV_FLAG_QUERY_BEGIN;
      printf("\fInitializing");
      initCommandLayer();
 
    configureAPN(&myXbee);
    return 0;
}
 
 
int main(void)
{
  int ret;
  EMU_DCDCInit_TypeDef dcdcInit = EMU_DCDCINIT_STK_DEFAULT;
  /* Chip errata */
  CHIP_Init();
 
  EMU_DCDCInit(&dcdcInit);
 
  if (SysTick_Config(CMU_ClockFreqGet(cmuClock_CORE) / 1000)) {
      while (1) ;
   }
 
  gpioSetup();
  I2CSPM_Init(&i2cInit);
  DISPLAY_Init();
 
  if (RETARGET_TextDisplayInit() != TEXTDISPLAY_EMSTATUS_OK) {
      while (1) ;
    }
 
  Xbee_init();
  delay(1000);
  printDeviceDescription();
  delay(4000);
 
  /* Wait for cell connection */
  printf("\fWaiting for signal");
  checkConnection();
  printf("\f Ready to send data");
  resolveIFTTTServerFqdn();
  delay(10);
  ret = sendDoorStatus("closed");
 
 
  if (ret >= 0)
  {
      printf("\f Door is closed");
  }else{
      printf("\f Error sending data");
      httpClientSend(&env, NULL, 0, 2, xbee_seconds_timer() + 5);
  }
  HallInit();
 
  /* Infinite loop */
  while (1) {
      // Check the door status
      if(door)
      {
        if(Justsent)
        {
            sendDoorStatus("opened");
            Justsent = false;
        }
        printf("\r Door is opened");
      }else{
        if(Justsent)
        {
            sendDoorStatus("closed");
            Justsent = false;
        }
        printf("\r Door is closed");
      }
 
      // Check Xbee for any changes
      xbee_dev_tick(&myXbee);
      delay(10);
  }
}
 
/*
 *  GPIO Interrupt Handler
 */
void GPIO_EVEN_IRQHandler(void)
{
    // Clear the interrupt
    GPIO_IntClear(1);
    // Change the door status
    door = !door;
    // Set Justsent to true to indicate the door status just changed
    Justsent = true;
}

Conclusion

The cellular expansion board is a great way to test adding LTE-M capabilities to your remote IoT device. This example is a way to get you started with showing a little of working with the EFM32G11 and making simple POST commands with the LTE-M module. There are more possibilities that can be added. The expansion board has a GNSS on it. I didn’t use it for my example, but if you need position and time it will be useful for your project. Hopefully this helps you get started on adding cellular to your wireless projects.

Useful Resources

By Ben R

Comments/Questions

Any questions or comments please go to our TechForum.