1151  LTE-M Interactive Lab

1151.1 Learning Objectives

By the end of this lab, you should be able to:

  • Understand LTE-M connection procedures including EPS bearer establishment
  • Experience VoLTE capability for voice-enabled IoT devices
  • Compare PSM and eDRX power modes between LTE-M and NB-IoT
  • Learn Coverage Enhancement Mode B for extended coverage
  • Observe seamless cell transitions (handover) that enable tracking at highway speeds
  • Compare Control Plane and User Plane data optimization paths

1151.2 Prerequisites

Before starting this lab, you should be familiar with:

1151.3 Lab Overview

NoteLab Overview

This interactive lab provides a hands-on simulation of LTE-M (LTE Cat-M1) concepts using an ESP32 microcontroller in Wokwi. While actual LTE-M communication requires specialized cellular modules and carrier connectivity, this simulation demonstrates the key concepts and procedures that differentiate LTE-M from NB-IoT and make it suitable for mobile IoT applications.

What You Will Learn:

  • LTE-M Connection Procedures: Understand the attach sequence including cell search, RRC connection, and EPS bearer establishment
  • VoLTE for IoT: Experience how Voice over LTE enables push-to-talk and emergency calling in IoT devices
  • PSM and eDRX Power Modes: Compare power-saving behavior in LTE-M versus NB-IoT configurations
  • Coverage Enhancement (CE) Mode B: Learn how LTE-M extends coverage to challenging RF environments
  • Mobility and Handover: Observe seamless cell transitions that enable tracking applications at highway speeds
  • Data Transmission Modes: Compare Control Plane and User Plane optimization paths for different payload sizes

Prerequisites: Basic understanding of C/C++ programming, familiarity with cellular IoT concepts from earlier sections, and knowledge of PSM/eDRX timer operation.

1151.4 Lab Environment

The simulation below runs on an ESP32 in Wokwi’s browser-based environment. The code simulates LTE-M modem behavior including realistic timing, state machines, handover procedures, and power calculations that highlight LTE-M’s unique capabilities for mobile IoT.

TipHow to Use This Lab
  1. Copy the code from the section below into the Wokwi editor
  2. Click β€œStart Simulation” to run the LTE-M simulation
  3. Open the Serial Monitor (115200 baud) to see detailed output
  4. Observe the state transitions including handover events as the simulated device moves
  5. Compare with NB-IoT: Note the differences in latency, handover support, and voice capability
  6. Try the challenge exercises to deepen your understanding of LTE-M design trade-offs

1151.5 Complete LTE-M Simulation Code

Copy this code into the Wokwi editor to run the simulation:

/*
 * LTE-M (Cat-M1) Fundamentals Lab - ESP32 Simulation
 *
 * This simulation demonstrates key LTE-M concepts that differentiate it from NB-IoT:
 * - Network attach procedure with EPS bearer establishment
 * - Full mobility support with inter-cell handover
 * - VoLTE capability for voice-enabled IoT devices
 * - PSM and eDRX power modes (similar to NB-IoT but with faster wake times)
 * - Coverage Enhancement Mode B for extended coverage
 * - User Plane vs Control Plane data optimization
 * - Lower latency (10-15ms) compared to NB-IoT (1.6-10s)
 *
 * Key LTE-M Advantages Over NB-IoT:
 * - Handover support (mobile devices up to 160 km/h)
 * - Higher data rate (1 Mbps vs 250 kbps)
 * - Lower latency (10-15ms vs 1.6-10s)
 * - VoLTE voice support
 * - Faster power-up and connection establishment
 *
 * Educational Purpose: Understanding LTE-M without actual cellular hardware
 *
 * Author: IoT Class Lab
 * License: MIT
 */

#include <Arduino.h>

// ============================================================================
// LTE-M Configuration Constants
// ============================================================================

// Network Configuration
#define PLMN_MCC "310"              // Mobile Country Code (US)
#define PLMN_MNC "260"              // Mobile Network Code (T-Mobile)
#define BAND_NUMBER 12              // LTE Band 12 (700 MHz) - common for LTE-M in US
#define EARFCN_SERVING 5110         // E-UTRA Absolute Radio Frequency Channel Number
#define EARFCN_NEIGHBOR 5230        // Neighbor cell EARFCN for handover demo

// LTE-M Specific Parameters (vs NB-IoT)
#define BANDWIDTH_MHZ 1.4           // LTE-M uses 1.4 MHz bandwidth (vs 180 kHz NB-IoT)
#define MAX_DATA_RATE_KBPS 1000     // 1 Mbps (vs 250 kbps NB-IoT)
#define LATENCY_MS 15               // Typical latency (vs 1.6-10s NB-IoT)
#define HANDOVER_SUPPORTED true     // Key differentiator from NB-IoT
#define VOLTE_SUPPORTED true        // Voice over LTE for IoT

// PSM Timer Configuration (T3412 - TAU timer, T3324 - Active timer)
// LTE-M typically uses shorter timers than NB-IoT due to mobility requirements
#define T3412_VALUE_HOURS 12        // TAU period (shorter than NB-IoT for mobile tracking)
#define T3324_VALUE_SECONDS 20      // Active timer before PSM (shorter for responsiveness)

// eDRX Configuration
// LTE-M supports eDRX but typically uses shorter cycles for faster response
#define EDRX_CYCLE_SECONDS 10.24    // Shorter than NB-IoT for mobile applications
#define PTW_SECONDS 1.28            // Paging Time Window

// Coverage Enhancement Mode B (LTE-M equivalent to NB-IoT CE levels)
// Mode A: Normal coverage (no repetitions)
// Mode B: Extended coverage (repetitions, but fewer than NB-IoT CE2)
#define CE_MODE_B_ENABLED true
#define MAX_REPETITIONS_MODE_B 32   // LTE-M caps at 32 (vs 128 for NB-IoT CE2)

// Power Consumption Values (typical for LTE-M modules - higher than NB-IoT)
#define POWER_PSM_UA 5              // PSM sleep: 5 Β΅A (similar to NB-IoT)
#define POWER_EDRX_MA 0.8           // eDRX sleep: 0.8 mA (slightly higher than NB-IoT)
#define POWER_IDLE_MA 15            // RRC Idle: 15 mA
#define POWER_RX_MA 80              // Receiving: 80 mA (higher than NB-IoT 46 mA)
#define POWER_TX_23DBM_MA 350       // Transmitting at 23 dBm: 350 mA
#define POWER_TX_20DBM_MA 250       // Transmitting at 20 dBm: 250 mA
#define POWER_HANDOVER_MA 120       // During handover: 120 mA

// VoLTE Parameters
#define VOLTE_CODEC "EVS"           // Enhanced Voice Services codec
#define VOLTE_BANDWIDTH_KBPS 13.2   // AMR-WB equivalent bandwidth
#define VOLTE_LATENCY_MS 100        // Voice latency requirement

// Mobility Parameters
#define MAX_SPEED_KMH 160           // LTE-M supports up to 160 km/h (vs 0 for NB-IoT)
#define HANDOVER_THRESHOLD_DBM -110 // Trigger handover when RSRP drops below
#define HANDOVER_HYSTERESIS_DB 3    // Hysteresis to prevent ping-pong
#define MEASUREMENT_REPORT_INTERVAL_MS 500

// Timing Constants (milliseconds)
#define CELL_SEARCH_TIME_MS 2000    // Faster than NB-IoT
#define RRC_CONNECT_TIME_MS 300     // LTE-M is faster
#define EPS_BEARER_SETUP_MS 200     // EPS bearer establishment
#define AUTHENTICATION_TIME_MS 500
#define HANDOVER_EXECUTION_MS 50    // Seamless handover time
#define TAU_PROCEDURE_TIME_MS 400
#define DATA_TRANSFER_TIME_MS 100   // Lower latency than NB-IoT

// ============================================================================
// Enumerations and Type Definitions
// ============================================================================

// LTE-M Device States (3GPP compliant with mobility extensions)
enum LTEMState {
  STATE_POWER_OFF,
  STATE_CELL_SEARCH,              // Searching for suitable cell
  STATE_CELL_SELECTION,           // S-criterion evaluation
  STATE_CELL_RESELECTION,         // Evaluating better cells (idle mode)
  STATE_RRC_IDLE,                 // Camped on cell, monitoring paging
  STATE_RRC_CONNECTING,           // Establishing RRC connection
  STATE_RRC_CONNECTED,            // Active connection to eNodeB
  STATE_EPS_BEARER_SETUP,         // Setting up default/dedicated bearers
  STATE_AUTHENTICATING,           // AKA (Authentication and Key Agreement)
  STATE_REGISTERED,               // EMM-REGISTERED, ECM-IDLE or CONNECTED
  STATE_DATA_TRANSFER,            // Active data session
  STATE_VOLTE_SETUP,              // Establishing VoLTE call
  STATE_VOLTE_ACTIVE,             // Voice call in progress
  STATE_HANDOVER_PREP,            // Handover preparation (measurement reports)
  STATE_HANDOVER_EXEC,            // Handover execution
  STATE_PSM,                      // Power Saving Mode (deep sleep)
  STATE_EDRX_SLEEP,               // eDRX sleep period
  STATE_EDRX_PAGING               // eDRX paging window
};

// Coverage Enhancement Modes (LTE-M specific)
enum CEMode {
  CE_MODE_A = 0,    // Normal coverage (no repetitions)
  CE_MODE_B = 1     // Extended coverage (up to 32 repetitions)
};

// Data Path Options
enum DataPath {
  PATH_CONTROL_PLANE,     // CP-CIoT: Data over NAS signaling (small payloads)
  PATH_USER_PLANE,        // UP: Traditional data bearer (larger payloads)
  PATH_VOLTE              // VoLTE: Voice over IMS
};

// Network Registration Status
enum RegistrationStatus {
  REG_NOT_REGISTERED = 0,
  REG_REGISTERED_HOME = 1,
  REG_SEARCHING = 2,
  REG_DENIED = 3,
  REG_UNKNOWN = 4,
  REG_REGISTERED_ROAMING = 5
};

// Handover State
enum HandoverState {
  HO_NONE,
  HO_MEASUREMENT,       // Sending measurement reports
  HO_PREPARATION,       // Network preparing target cell
  HO_EXECUTION,         // Switching to target cell
  HO_COMPLETE           // Handover successful
};

// Power Mode Configuration
enum PowerMode {
  POWER_MODE_ALWAYS_ON,         // Maximum responsiveness
  POWER_MODE_DRX,               // Connected mode DRX
  POWER_MODE_EDRX_ONLY,         // eDRX without PSM
  POWER_MODE_PSM_ONLY,          // PSM without eDRX
  POWER_MODE_PSM_EDRX           // Combined for maximum battery
};

// ============================================================================
// Data Structures
// ============================================================================

// Signal Quality Measurements
struct SignalQuality {
  int rsrp;             // Reference Signal Received Power (-140 to -44 dBm)
  int rsrq;             // Reference Signal Received Quality (-20 to -3 dB)
  int sinr;             // Signal to Interference+Noise Ratio (-20 to 30 dB)
  int rssi;             // Received Signal Strength Indicator
  int cellId;           // Physical Cell ID (0-503)
  int earfcn;           // Frequency channel
  int ta;               // Timing Advance (distance indication)
};

// Neighbor Cell Information (for handover)
struct NeighborCell {
  int cellId;
  int earfcn;
  int rsrp;
  int rsrq;
  bool suitable;        // Meets S-criterion
};

// Network Information
struct NetworkInfo {
  char mcc[4];
  char mnc[4];
  int band;
  int tac;              // Tracking Area Code
  bool attached;
  RegistrationStatus regStatus;
  bool epsBearer;       // EPS bearer established
  bool imsRegistered;   // IMS registration for VoLTE
};

// Mobility Statistics
struct MobilityStats {
  int handoverCount;
  int handoverFailures;
  float avgHandoverTime;
  int cellReselections;
  float currentSpeedKmh;
  float distanceTraveledKm;
};

// Power Statistics
struct PowerStats {
  float totalEnergyMah;
  unsigned long psmDurationMs;
  unsigned long edrxDurationMs;
  unsigned long connectedDurationMs;
  unsigned long txDurationMs;
  unsigned long rxDurationMs;
  unsigned long handoverDurationMs;
  int transmissionCount;
  int pageCheckCount;
};

// VoLTE Call Statistics
struct VoLTEStats {
  int callsAttempted;
  int callsSuccessful;
  float avgSetupTimeMs;
  float avgCallDurationSec;
  int handoversDuringCall;
};

// Timer State
struct TimerState {
  unsigned long t3412StartMs;       // TAU timer
  unsigned long t3324StartMs;       // Active timer
  unsigned long edrxCycleStartMs;
  unsigned long measurementReportMs;
  bool t3412Running;
  bool t3324Running;
  bool psmEnabled;
  bool edrxEnabled;
};

// Data Transfer Statistics
struct DataStats {
  int totalBytesSent;
  int totalBytesReceived;
  int messagesSent;
  int messagesReceived;
  float averageLatencyMs;
  int cpPathMessages;     // Control Plane path
  int upPathMessages;     // User Plane path
};

// ============================================================================
// Global Variables
// ============================================================================

LTEMState currentState = STATE_POWER_OFF;
CEMode currentCEMode = CE_MODE_A;
PowerMode powerMode = POWER_MODE_PSM_EDRX;
DataPath currentDataPath = PATH_USER_PLANE;
HandoverState handoverState = HO_NONE;

SignalQuality servingCell;
NeighborCell neighborCells[3];
NetworkInfo networkInfo;
MobilityStats mobilityStats;
PowerStats powerStats;
VoLTEStats volteStats;
TimerState timerState;
DataStats dataStats;

unsigned long stateEntryTime = 0;
unsigned long lastStatusUpdate = 0;
unsigned long simulationStartTime = 0;
unsigned long lastMeasurementReport = 0;

int repetitionCount = 1;
bool pendingDownlink = false;
bool mobilitySimEnabled = true;
bool volteCallActive = false;
int sensorReadingCounter = 0;
float simulatedSpeedKmh = 0;

// ============================================================================
// Function Declarations
// ============================================================================

void initializeModem();
void processStateMachine();
void updatePowerConsumption();
void printStatus();
void printLTEMvsNBIoTComparison();
void simulateMobility();
void evaluateHandoverNeed();
void sendMeasurementReport();
void initiateVoLTECall();
void handleVoLTECall();
void endVoLTECall();
void enterPSM();
void exitPSM();
void handleEDRXCycle();
void sendUplinkData(const char* data, int length, DataPath path);
void printStateTransition(LTEMState from, LTEMState to);
void printPowerModeInfo();
const char* getStateName(LTEMState state);
const char* getCEModeName(CEMode mode);

// ============================================================================
// Setup Function
// ============================================================================

void setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.println();
  Serial.println("======================================================================");
  Serial.println("          LTE-M (Cat-M1) FUNDAMENTALS INTERACTIVE LAB");
  Serial.println("======================================================================");
  Serial.println();
  Serial.println("  This simulation demonstrates LTE-M concepts including:");
  Serial.println("  * Network attach with EPS bearer establishment");
  Serial.println("  * Full mobility support (handover up to 160 km/h)");
  Serial.println("  * VoLTE capability for voice-enabled IoT");
  Serial.println("  * PSM and eDRX power modes (faster than NB-IoT)");
  Serial.println("  * Coverage Enhancement Mode B");
  Serial.println("  * User Plane vs Control Plane data optimization");
  Serial.println();
  Serial.println("======================================================================");
  Serial.println();

  initializeModem();
  simulationStartTime = millis();

  Serial.println("----------------------------------------------------------------------");
  Serial.println(" LTE-M CONFIGURATION");
  Serial.println("----------------------------------------------------------------------");
  Serial.printf(" PLMN: %s-%s | Band: %d | EARFCN: %d\n",
                PLMN_MCC, PLMN_MNC, BAND_NUMBER, EARFCN_SERVING);
  Serial.printf(" Bandwidth: %.1f MHz | Max Rate: %d kbps | Latency: %d ms\n",
                BANDWIDTH_MHZ, MAX_DATA_RATE_KBPS, LATENCY_MS);
  Serial.printf(" T3412 (TAU): %d hours | T3324 (Active): %d seconds\n",
                T3412_VALUE_HOURS, T3324_VALUE_SECONDS);
  Serial.printf(" Handover: %s | VoLTE: %s | Max Speed: %d km/h\n",
                HANDOVER_SUPPORTED ? "ENABLED" : "DISABLED",
                VOLTE_SUPPORTED ? "ENABLED" : "DISABLED",
                MAX_SPEED_KMH);
  Serial.println("----------------------------------------------------------------------");
  Serial.println();

  printLTEMvsNBIoTComparison();
  Serial.println();
  printPowerModeInfo();
  Serial.println();

  Serial.println(">>> Initiating LTE-M network attach procedure...");
  Serial.println();
}

// ============================================================================
// Main Loop
// ============================================================================

void loop() {
  processStateMachine();
  updatePowerConsumption();

  if (mobilitySimEnabled && currentState >= STATE_REGISTERED) {
    simulateMobility();
  }

  if (currentState == STATE_REGISTERED || currentState == STATE_DATA_TRANSFER) {
    evaluateHandoverNeed();
  }

  if (currentState >= STATE_RRC_CONNECTED && currentState <= STATE_DATA_TRANSFER) {
    if (millis() - lastMeasurementReport > MEASUREMENT_REPORT_INTERVAL_MS) {
      sendMeasurementReport();
      lastMeasurementReport = millis();
    }
  }

  if (millis() - lastStatusUpdate > 5000) {
    printStatus();
    lastStatusUpdate = millis();
  }

  // Simulate sensor readings and data transmission
  static unsigned long lastSensorRead = 0;
  if (currentState == STATE_REGISTERED || currentState == STATE_PSM ||
      currentState == STATE_EDRX_SLEEP) {
    if (millis() - lastSensorRead > 25000) {
      sensorReadingCounter++;

      if (sensorReadingCounter % 3 == 0) {
        char largePayload[200];
        snprintf(largePayload, sizeof(largePayload),
                 "{\"id\":%d,\"type\":\"full\",\"lat\":37.7749,\"lon\":-122.4194,"
                 "\"speed\":%.1f,\"heading\":275,\"temp\":23.5,\"batt\":87,"
                 "\"cell\":%d,\"rsrp\":%d}",
                 sensorReadingCounter, simulatedSpeedKmh, servingCell.cellId,
                 servingCell.rsrp);
        sendUplinkData(largePayload, strlen(largePayload), PATH_USER_PLANE);
      } else {
        char smallPayload[50];
        snprintf(smallPayload, sizeof(smallPayload),
                 "{\"id\":%d,\"loc\":1,\"v\":%.0f}",
                 sensorReadingCounter, simulatedSpeedKmh);
        sendUplinkData(smallPayload, strlen(smallPayload), PATH_CONTROL_PLANE);
      }
      lastSensorRead = millis();
    }
  }

  // Simulate occasional VoLTE call
  static unsigned long lastVoLTEDemo = 0;
  if (VOLTE_SUPPORTED && currentState == STATE_REGISTERED) {
    if (millis() - lastVoLTEDemo > 60000 && !volteCallActive) {
      Serial.println();
      Serial.println(">>> VoLTE DEMONSTRATION: Initiating voice call...");
      initiateVoLTECall();
      lastVoLTEDemo = millis();
    }
  }

  if (volteCallActive) {
    handleVoLTECall();
  }

  delay(100);
}

// ============================================================================
// Implementation Functions (abbreviated for space - see full code in original)
// ============================================================================

void initializeModem() {
  servingCell.rsrp = -95;
  servingCell.rsrq = -10;
  servingCell.sinr = 8;
  servingCell.cellId = 142;
  servingCell.earfcn = EARFCN_SERVING;

  neighborCells[0] = {143, EARFCN_NEIGHBOR, -105, -12, true};
  neighborCells[1] = {144, EARFCN_SERVING, -108, -13, true};
  neighborCells[2] = {145, EARFCN_NEIGHBOR, -115, -15, false};

  strcpy(networkInfo.mcc, PLMN_MCC);
  strcpy(networkInfo.mnc, PLMN_MNC);
  networkInfo.band = BAND_NUMBER;
  networkInfo.attached = false;
  networkInfo.regStatus = REG_NOT_REGISTERED;

  memset(&mobilityStats, 0, sizeof(mobilityStats));
  memset(&powerStats, 0, sizeof(powerStats));
  memset(&volteStats, 0, sizeof(volteStats));
  memset(&dataStats, 0, sizeof(dataStats));

  timerState.psmEnabled = true;
  timerState.edrxEnabled = true;

  currentState = STATE_CELL_SEARCH;
  stateEntryTime = millis();

  Serial.println("[MODEM] LTE-M modem initialized");
}

void processStateMachine() {
  unsigned long timeInState = millis() - stateEntryTime;
  LTEMState previousState = currentState;

  switch (currentState) {
    case STATE_CELL_SEARCH:
      if (timeInState >= CELL_SEARCH_TIME_MS) {
        Serial.println("[ATTACH] Cell search complete");
        Serial.printf("         Cell ID: %d | RSRP: %d dBm\n",
                      servingCell.cellId, servingCell.rsrp);
        currentState = STATE_CELL_SELECTION;
        stateEntryTime = millis();
      }
      break;

    case STATE_CELL_SELECTION:
      if (timeInState >= 500) {
        currentCEMode = (servingCell.rsrp < -115) ? CE_MODE_B : CE_MODE_A;
        Serial.printf("[ATTACH] CE Mode %s selected\n", getCEModeName(currentCEMode));
        currentState = STATE_RRC_CONNECTING;
        stateEntryTime = millis();
      }
      break;

    case STATE_RRC_CONNECTING:
      if (timeInState >= RRC_CONNECT_TIME_MS) {
        Serial.println("[ATTACH] RRC Connection established");
        currentState = STATE_EPS_BEARER_SETUP;
        stateEntryTime = millis();
      }
      break;

    case STATE_EPS_BEARER_SETUP:
      if (timeInState >= EPS_BEARER_SETUP_MS) {
        networkInfo.epsBearer = true;
        Serial.println("[ATTACH] EPS Bearer established");
        currentState = STATE_AUTHENTICATING;
        stateEntryTime = millis();
      }
      break;

    case STATE_AUTHENTICATING:
      if (timeInState >= AUTHENTICATION_TIME_MS) {
        networkInfo.attached = true;
        networkInfo.regStatus = REG_REGISTERED_HOME;
        timerState.t3412Running = true;
        timerState.t3412StartMs = millis();

        Serial.println();
        Serial.println("======================================================================");
        Serial.println(" LTE-M ATTACH COMPLETE - DEVICE REGISTERED");
        Serial.printf(" Total attach time: %lu ms\n", millis() - simulationStartTime);
        Serial.println("======================================================================");

        if (VOLTE_SUPPORTED) {
          networkInfo.imsRegistered = true;
          Serial.println("[VoLTE] IMS registration successful");
        }
        currentState = STATE_REGISTERED;
        stateEntryTime = millis();
      }
      break;

    case STATE_REGISTERED:
      if (timerState.psmEnabled && timerState.t3324Running) {
        if (millis() - timerState.t3324StartMs >= T3324_VALUE_SECONDS * 1000) {
          enterPSM();
        }
      }
      if (!timerState.t3324Running) {
        timerState.t3324Running = true;
        timerState.t3324StartMs = millis();
      }
      break;

    case STATE_DATA_TRANSFER:
      if (timeInState >= DATA_TRANSFER_TIME_MS) {
        dataStats.messagesSent++;
        currentState = STATE_REGISTERED;
        stateEntryTime = millis();
        timerState.t3324StartMs = millis();
      }
      break;

    case STATE_HANDOVER_PREP:
      if (timeInState >= 100) {
        currentState = STATE_HANDOVER_EXEC;
        stateEntryTime = millis();
      }
      break;

    case STATE_HANDOVER_EXEC:
      if (timeInState >= HANDOVER_EXECUTION_MS) {
        Serial.println("[HO] HANDOVER COMPLETE!");
        Serial.printf("     Execution time: %d ms (seamless)\n", HANDOVER_EXECUTION_MS);
        servingCell.cellId = neighborCells[0].cellId;
        servingCell.rsrp = neighborCells[0].rsrp + 15;
        mobilityStats.handoverCount++;
        currentState = STATE_REGISTERED;
        stateEntryTime = millis();
      }
      break;

    case STATE_PSM:
      if (timeInState >= 30000) {
        Serial.println("[PSM] Wake event - exiting PSM");
        exitPSM();
      }
      break;

    default:
      break;
  }

  if (previousState != currentState) {
    printStateTransition(previousState, currentState);
  }
}

void simulateMobility() {
  static unsigned long lastUpdate = 0;
  if (millis() - lastUpdate < 1000) return;
  lastUpdate = millis();

  static float targetSpeed = 60;
  static int counter = 0;
  if (++counter >= 10) {
    targetSpeed = random(20, 120);
    counter = 0;
  }

  if (simulatedSpeedKmh < targetSpeed) simulatedSpeedKmh += 2;
  else if (simulatedSpeedKmh > targetSpeed) simulatedSpeedKmh -= 3;
  simulatedSpeedKmh = constrain(simulatedSpeedKmh, 0, MAX_SPEED_KMH);

  mobilityStats.distanceTraveledKm += (simulatedSpeedKmh / 3600.0);
  mobilityStats.currentSpeedKmh = simulatedSpeedKmh;

  static float degradation = 0;
  degradation += (simulatedSpeedKmh / 1000.0);
  if (degradation > 1) {
    servingCell.rsrp -= 1;
    neighborCells[0].rsrp += random(-1, 2);
    degradation = 0;
  }
  servingCell.rsrp = constrain(servingCell.rsrp, -130, -70);
}

void evaluateHandoverNeed() {
  if (handoverState != HO_NONE && handoverState != HO_COMPLETE) return;

  bool needHandover = (neighborCells[0].rsrp > servingCell.rsrp + HANDOVER_HYSTERESIS_DB) ||
                      (servingCell.rsrp < HANDOVER_THRESHOLD_DBM);

  if (needHandover) {
    Serial.println();
    Serial.println("----------------------------------------------------------------------");
    Serial.println(" HANDOVER TRIGGERED - LTE-M Mobility in Action!");
    Serial.printf(" Serving cell: %d dBm | Target cell: %d dBm | Speed: %.1f km/h\n",
                  servingCell.rsrp, neighborCells[0].rsrp, simulatedSpeedKmh);
    Serial.println("----------------------------------------------------------------------");
    currentState = STATE_HANDOVER_PREP;
    stateEntryTime = millis();
  }
}

void sendMeasurementReport() {
  for (int i = 0; i < 3; i++) {
    neighborCells[i].rsrp += random(-2, 3);
    neighborCells[i].rsrp = constrain(neighborCells[i].rsrp, -130, -80);
  }
}

void initiateVoLTECall() {
  if (!VOLTE_SUPPORTED || !networkInfo.imsRegistered) return;

  Serial.println("----------------------------------------------------------------------");
  Serial.println(" VoLTE CALL - LTE-M Unique Feature (NB-IoT cannot do this!)");
  Serial.println("----------------------------------------------------------------------");
  volteStats.callsAttempted++;
  currentState = STATE_VOLTE_SETUP;
  stateEntryTime = millis();
}

void handleVoLTECall() {
  static unsigned long callStart = 0;
  if (currentState == STATE_VOLTE_SETUP && millis() - stateEntryTime >= 800) {
    Serial.println("[VoLTE] Call connected");
    volteStats.callsSuccessful++;
    currentState = STATE_VOLTE_ACTIVE;
    callStart = millis();
    volteCallActive = true;
  }

  if (currentState == STATE_VOLTE_ACTIVE && millis() - callStart >= 5000) {
    endVoLTECall();
    callStart = 0;
  }
}

void endVoLTECall() {
  Serial.println("[VoLTE] Call ended - 5 seconds HD voice");
  volteCallActive = false;
  currentState = STATE_REGISTERED;
  stateEntryTime = millis();
}

void enterPSM() {
  Serial.println("[PSM] Entering Power Saving Mode (5 Β΅A)");
  currentState = STATE_PSM;
  stateEntryTime = millis();
  timerState.t3324Running = false;
}

void exitPSM() {
  powerStats.psmDurationMs += millis() - stateEntryTime;
  currentState = STATE_RRC_CONNECTING;
  stateEntryTime = millis();
}

void handleEDRXCycle() {
  // eDRX cycle handling (simplified)
}

void sendUplinkData(const char* data, int length, DataPath path) {
  if (currentState == STATE_PSM) exitPSM();

  Serial.println();
  Serial.printf("[DATA] Uplink #%d | %d bytes | %s path | %d ms latency\n",
                sensorReadingCounter, length,
                path == PATH_CONTROL_PLANE ? "CP" : "UP", LATENCY_MS);

  dataStats.totalBytesSent += length;
  if (path == PATH_CONTROL_PLANE) dataStats.cpPathMessages++;
  else dataStats.upPathMessages++;

  currentState = STATE_DATA_TRANSFER;
  stateEntryTime = millis();
}

void updatePowerConsumption() {
  static unsigned long lastUpdate = 0;
  unsigned long elapsed = millis() - lastUpdate;
  if (elapsed < 100) return;
  lastUpdate = millis();

  float currentMa;
  switch (currentState) {
    case STATE_PSM: currentMa = POWER_PSM_UA / 1000.0; break;
    case STATE_DATA_TRANSFER: currentMa = POWER_TX_23DBM_MA; break;
    case STATE_HANDOVER_PREP:
    case STATE_HANDOVER_EXEC: currentMa = POWER_HANDOVER_MA; break;
    default: currentMa = POWER_IDLE_MA; break;
  }

  powerStats.totalEnergyMah += currentMa * (elapsed / 3600000.0);
}

void printStatus() {
  unsigned long runTime = millis() - simulationStartTime;

  Serial.println();
  Serial.println("======================================================================");
  Serial.printf(" State: %s | Run time: %lu s | Speed: %.1f km/h\n",
                getStateName(currentState), runTime / 1000, simulatedSpeedKmh);
  Serial.printf(" Signal: RSRP %d dBm | Cell: %d | Handovers: %d\n",
                servingCell.rsrp, servingCell.cellId, mobilityStats.handoverCount);
  Serial.printf(" Data: %d msgs sent | Energy: %.4f mAh\n",
                dataStats.messagesSent, powerStats.totalEnergyMah);
  Serial.println("======================================================================");
}

void printLTEMvsNBIoTComparison() {
  Serial.println("----------------------------------------------------------------------");
  Serial.println(" LTE-M vs NB-IoT COMPARISON");
  Serial.println("----------------------------------------------------------------------");
  Serial.println(" Feature          | LTE-M (Cat-M1)    | NB-IoT (Cat-NB1)");
  Serial.println("------------------|-------------------|-------------------");
  Serial.println(" Data Rate        | 1 Mbps            | 250 kbps");
  Serial.println(" Latency          | 10-15 ms          | 1.6-10 seconds");
  Serial.println(" Mobility         | YES (160 km/h)    | NO");
  Serial.println(" VoLTE            | YES               | NO");
  Serial.println(" MCL              | 156 dB            | 164 dB");
  Serial.println("----------------------------------------------------------------------");
}

void printPowerModeInfo() {
  Serial.println("----------------------------------------------------------------------");
  Serial.println(" POWER MODE: PSM + eDRX Combined");
  Serial.printf(" PSM Sleep: %d Β΅A | eDRX Sleep: %.1f mA | TX: %d mA\n",
                POWER_PSM_UA, POWER_EDRX_MA, POWER_TX_23DBM_MA);
  Serial.println("----------------------------------------------------------------------");
}

void printStateTransition(LTEMState from, LTEMState to) {
  Serial.printf("[STATE] %s -> %s\n", getStateName(from), getStateName(to));
}

const char* getStateName(LTEMState state) {
  switch (state) {
    case STATE_POWER_OFF: return "POWER_OFF";
    case STATE_CELL_SEARCH: return "CELL_SEARCH";
    case STATE_CELL_SELECTION: return "CELL_SELECTION";
    case STATE_RRC_CONNECTING: return "RRC_CONNECTING";
    case STATE_EPS_BEARER_SETUP: return "EPS_BEARER_SETUP";
    case STATE_AUTHENTICATING: return "AUTHENTICATING";
    case STATE_REGISTERED: return "REGISTERED";
    case STATE_DATA_TRANSFER: return "DATA_TRANSFER";
    case STATE_VOLTE_SETUP: return "VOLTE_SETUP";
    case STATE_VOLTE_ACTIVE: return "VOLTE_ACTIVE";
    case STATE_HANDOVER_PREP: return "HANDOVER_PREP";
    case STATE_HANDOVER_EXEC: return "HANDOVER_EXEC";
    case STATE_PSM: return "PSM";
    default: return "UNKNOWN";
  }
}

const char* getCEModeName(CEMode mode) {
  return (mode == CE_MODE_A) ? "A (Normal)" : "B (Extended)";
}

1151.6 Challenge Exercises

WarningChallenge 1: Optimize for Vehicle Tracking

Objective: Configure LTE-M parameters for a GPS tracker on delivery trucks.

Requirements: - Position updates every 30 seconds while moving - Battery life target: 2+ years with 5000 mAh battery - Must handle highway speeds (up to 120 km/h) - Quick response to geofence alerts (< 30 second latency)

Tasks: 1. Modify T3324_VALUE_SECONDS and EDRX_CYCLE_SECONDS for optimal balance 2. Test different scenarios by adjusting simulatedSpeedKmh 3. Calculate whether PSM is appropriate for this use case

Analysis Questions: - Why is eDRX more suitable than PSM for this application? - What happens if the device enters PSM while crossing a geofence? - How does handover frequency affect battery life at highway speeds?

WarningChallenge 2: Compare LTE-M and NB-IoT Latency

Objective: Quantify the latency advantage of LTE-M for time-sensitive applications.

Scenario: Emergency button device that must alert monitoring center within 2 seconds.

Tasks: 1. Measure simulated end-to-end latency in LTE-M (observe LATENCY_MS) 2. Compare with NB-IoT specifications (1.6-10 second latency) 3. Determine if PSM can be used while meeting the 2-second requirement

Analysis Questions: - Why does NB-IoT have 100x higher latency than LTE-M? - How does eDRX cycle length affect response time? - What power mode would you recommend for emergency buttons?

WarningChallenge 3: VoLTE Emergency Call Simulation

Objective: Understand VoLTE capability for IoT emergency devices.

Scenario: Elevator intercom that must maintain voice quality during building transit.

Tasks: 1. Observe VoLTE call setup and duration in the simulation 2. Note handover behavior during active calls 3. Calculate bandwidth requirements for continuous voice

Analysis Questions: - Why can NB-IoT not support VoLTE? - How does handover during a call differ from handover during data transfer? - What bandwidth does VoLTE require and how does this compare to sensor data?

1151.7 Expected Outcomes

TipLearning Outcomes Checklist

After completing this lab, you should be able to:

1151.8 Real-World Considerations

ImportantSimulation vs Reality

This simulation demonstrates LTE-M concepts but differs from real deployments:

What this simulation shows accurately: - State machine progression and timing relationships - Handover trigger conditions and execution flow - Power consumption ratios between operating states - VoLTE call setup sequence - Data path selection logic (CP vs UP) - Comparative advantages over NB-IoT

What requires real hardware/network: - Actual RF measurements and handover execution - Real carrier authentication with USIM - True power consumption profiling - Network-negotiated PSM/eDRX parameters - IMS registration and VoLTE codec negotiation - Roaming and inter-carrier handover

For hands-on LTE-M development: - Use modules like Quectel BG96, u-blox SARA-R4, Nordic nRF9160, or Sequans Monarch - Obtain LTE-M SIM cards from carriers (T-Mobile, AT&T, Verizon in US) - Consider global IoT platforms: Hologram, 1NCE, emnify for multi-carrier support - Test in real mobility scenarios (vehicle, train) to validate handover behavior

1151.9 Summary

  • LTE-M simulation demonstrates key differentiators from NB-IoT: mobility, latency, VoLTE, and higher data rates
  • Handover support enables tracking applications at highway speeds (up to 160 km/h) with 50-100 ms interruptions
  • VoLTE capability allows voice-enabled IoT devices like emergency buttons and intercoms
  • Power optimization through PSM and eDRX works similarly to NB-IoT but with shorter cycles for faster response
  • Data path selection between Control Plane and User Plane optimizes for payload size and latency requirements

1151.10 What’s Next

Continue your cellular IoT journey: