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:
- Cellular IoT Overview: Understanding cellular IoT basics
- NB-IoT vs LTE-M Comparison: Technology differences
- Cellular IoT Power Optimization: PSM and eDRX concepts
- Basic C/C++ programming: For understanding the simulation code
1151.3 Lab 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.
- Copy the code from the section below into the Wokwi editor
- Click βStart Simulationβ to run the LTE-M simulation
- Open the Serial Monitor (115200 baud) to see detailed output
- Observe the state transitions including handover events as the simulated device moves
- Compare with NB-IoT: Note the differences in latency, handover support, and voice capability
- 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
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?
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?
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
After completing this lab, you should be able to:
1151.8 Real-World Considerations
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:
- NB-IoT comparison: Try NB-IoT Labs and Implementation for the stationary sensor perspective
- Deployment planning: Apply learnings to Cellular IoT Deployment Planning
- Global connectivity: Learn eSIM and Global Deployment for multi-carrier strategies
- LPWAN comparison: Contrast with LoRaWAN for unlicensed alternatives