Skip to content

ESP32 + SIM800C 2G GPRS Telemetry to SiliconWit IO

In this tutorial, you will learn how to send IoT telemetry data from an ESP32 microcontroller to the SiliconWit IO platform using a SIM800C 2G GPRS cellular module. This is ideal for remote deployments where WiFi is not available — such as agricultural monitoring, weather stations, asset tracking, and industrial sensors in the field.

The ESP32 communicates with the SIM800C over UART, establishes a GPRS data connection through the cellular network, and publishes MQTT messages over a secure TLS connection to the SiliconWit IO cloud.

  • How to wire a SIM800C module to an ESP32
  • How to establish a 2G GPRS data connection using TinyGSM
  • How to handle TLS encryption on the ESP32 (since SIM800C only supports plain TCP)
  • How to publish telemetry data to SiliconWit IO over MQTT
  • Practical considerations and limitations of 2G GPRS for IoT
ComponentDescription
ESP32 Development BoardAny ESP32, ESP32-S3, or ESP32-C3 board
SIM800C GSM/GPRS Module2G cellular modem (SIM800L or SIM800 variants also work)
SIM CardActive SIM with 2G data plan from your mobile carrier
Power Supply3.7V–4.2V LiPo battery or regulated supply for the SIM800C (draws up to 2A peak)
Jumper WiresFor connecting ESP32 to SIM800C (TX, RX, GND)
USB CableFor programming and powering the ESP32
AntennaGSM antenna for the SIM800C (usually included with the module)
SoftwarePurpose
PlatformIOBuild system and IDE (VSCode extension or CLI)
SiliconWit IO AccountFree IoT platform account
LibraryVersionPurpose
TinyGSM^0.11.7AT command interface for GSM/GPRS modems
ESP_SSLClient^2.1.7TLS/SSL handled by the ESP32 (not the SIM800C)
PubSubClient^2.8MQTT client for Arduino
ArduinoJson^7.0.0JSON serialization

Why TLS is Handled by the ESP32, Not the SIM800C

Section titled “Why TLS is Handled by the ESP32, Not the SIM800C”

The SiliconWit IO MQTT broker requires TLS 1.2 on port 8883. The SIM800C module only supports up to TLS 1.0, which modern servers reject.

The solution is a layered communication stack:

┌──────────────────────────────────────────────────┐
│ SiliconWit IO Cloud │
│ MQTT Broker (TLS 1.2, port 8883) │
└────────────────────┬─────────────────────────────┘
│ TLS 1.2 (encrypted)
┌────────────────────┴─────────────────────────────┐
│ ESP32 (ESP_SSLClient) │
│ Handles TLS handshake and encryption │
│ using hardware crypto acceleration │
└────────────────────┬─────────────────────────────┘
│ Plain TCP (unencrypted, internal)
┌────────────────────┴─────────────────────────────┐
│ SIM800C (TinyGSM Client) │
│ Handles GPRS connection and raw TCP │
│ transport over the cellular network │
└────────────────────┬─────────────────────────────┘
│ 2G Cellular (GPRS)
┌────────────────────┴─────────────────────────────┐
│ Cell Tower / Mobile Network │
│ → Internet │
└──────────────────────────────────────────────────┘

Stack summary: SIM800C (plain TCP) → ESP32 (TLS 1.2) → MQTT Broker

The ESP32’s hardware crypto acceleration makes TLS handshakes fast even over the slow 2G link.

┌─────────────────────────────────────────────────────────┐
│ SiliconWit IO Cloud │
│ │
│ Dashboard ←→ MQTT Broker (TLS on port 8883) │
│ ↕ │
│ Telemetry Topic │
│ d/{device_id}/t │
└────────────────────┬────────────────────────────────────┘
2G Cellular Network
┌────────────────────┴────────────────────────────────────┐
│ │
│ ESP32 SIM800C │
│ ┌──────────┐ UART (9600) ┌──────────┐ │
│ │ GPIO 4 │←───── TXD ───────│ TXD │ │
│ │ GPIO 2 │─────→ RXD ───────│ RXD │ │
│ │ GND │──────────────────│ GND │ ┌─────┐ │
│ └──────────┘ └──────────┘ │ SIM │ │
│ └─────┘ │
│ Handles: Handles: │
│ - TLS encryption - AT commands │
│ - MQTT protocol - GPRS connection │
│ - JSON serialization - Raw TCP transport │
│ - Sensor reading - Cellular network │
│ │
└─────────────────────────────────────────────────────────┘

Note on Subscribe/Commands: 2G GPRS has high latency and unreliable persistent connections, making MQTT subscribe impractical. This tutorial focuses on publish-only (sending data to the cloud), which works reliably over 2G. For bidirectional control, use WiFi (see the WiFi MQTT Relay Control tutorial) or upgrade to a 4G module.

Step 1: Create a SiliconWit IO Account and Register a Device

Section titled “Step 1: Create a SiliconWit IO Account and Register a Device”
  1. Go to siliconwit.io/register and create a free account.
  2. Verify your email and log in to the dashboard.
  3. Navigate to Devices and click Add Device.
  4. Give your device a name (e.g., “ESP32 GPRS Sensor”).
  5. Note down these credentials:
CredentialExampleDescription
Device IDxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxUnique device identifier (also used as MQTT username)
Access Tokenyour_access_token_hereMQTT password
Publish Topicd/{device_id}/tTopic for sending data (d = device, t = telemetry)

Keep your access token secure. Never share it publicly or commit it to version control.

  1. Insert the SIM card into the SIM800C module’s SIM tray (usually a micro-SIM slot).
  2. Ensure 2G data is active on the SIM card. Contact your carrier if unsure.
  3. Find your carrier’s APN settings. Common examples:
CountryCarrierAPNUsernamePassword
KenyaSafaricomsafaricomsafdata
KenyaAirtelinternet(empty)(empty)
USAT-Mobileepc.tmobile.com(empty)(empty)
USAAT&Tbroadband(empty)(empty)
UKVodafoneinternetwebweb
IndiaJiojionet(empty)(empty)
GlobalHologramhologram(empty)(empty)

If you do not know your carrier’s APN, search for “[Your Carrier] APN settings” or contact customer support. Using the wrong APN is the most common cause of GPRS connection failures.

SIM800C PinESP32 PinDescription
TXDGPIO 4SIM800C transmits → ESP32 receives
RXDGPIO 2ESP32 transmits → SIM800C receives
GNDGNDCommon ground (required!)
VCCExternal 3.7–4.2VDo NOT power from ESP32’s 3.3V pin
ESP32 SIM800C
┌──────────┐ ┌──────────────┐
│ │ │ │
│ GPIO 4 │←─── UART ────→│ TXD │
│ (RX) │ │ │
│ │ │ │
│ GPIO 2 │──── UART ────→│ RXD │
│ (TX) │ │ │
│ │ │ │ ┌─────────┐
│ GND ├────────────────│ GND │ │ SIM │
│ │ │ │ │ Card │
└──────────┘ │ VCC ←───────┼────┐ └─────────┘
└──────────────┘ │
┌────┴────┐
│ 3.7V │
│ LiPo │
│ Battery │
└─────────┘

The SIM800C module draws up to 2A during transmission bursts. The ESP32’s 3.3V regulator cannot supply this. You must use a separate power source:

Power OptionVoltageNotes
3.7V LiPo battery3.7–4.2VBest option, handles current spikes
LM2596 buck converterSet to 4.0VFrom 5V USB or 12V supply
18650 Li-ion cell3.7V nominalGood for portable deployments

Common symptom of inadequate power: The SIM800C resets repeatedly, the network LED blinks fast but never stabilizes, or AT commands return garbage characters.

LED BehaviorMeaning
Blinks every 1 secondSearching for network
Blinks every 2 secondsConnected to network (GPRS ready)
Blinks every 3 secondsConnected and data session active
Always onModule not powered correctly

Wait until the LED blinks every 2–3 seconds before attempting to connect.

project/
├── src/
│ └── main.cpp
├── platformio.ini
└── ...
; PlatformIO Project Configuration File
[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
upload_port = /dev/ttyACM0
monitor_port = /dev/ttyACM0
upload_flags = --before=no_reset
build_flags =
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
lib_deps =
vshymanskyy/TinyGSM@^0.11.7
knolleary/PubSubClient@^2.8
bblanchon/ArduinoJson@^7.0.0
mobizt/ESP_SSLClient@^2.1.7

Board Selection: Change the board value to match your ESP32 variant:

  • ESP32-S3: esp32-s3-devkitc-1
  • ESP32-C3: esp32-c3-devkitm-1
  • ESP32 (original): esp32dev

USB CDC Flags: Only needed for ESP32-S3/C3 boards using built-in USB for serial. Remove if your board has a dedicated USB-to-UART chip.

Create src/main.cpp with the following code:

#define TINY_GSM_MODEM_SIM800
#define TINY_GSM_RX_BUFFER 1024
#include <Arduino.h>
#include <TinyGsmClient.h>
#include <ESP_SSLClient.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
// ========== PIN DEFINITIONS ==========
// ESP32 GPIO4 ← SIM800C TXD (ESP32 receives from SIM800C)
// ESP32 GPIO2 → SIM800C RXD (ESP32 sends to SIM800C)
#define SIM800C_RX_PIN 4
#define SIM800C_TX_PIN 2
// ========== GPRS CREDENTIALS ==========
// Change these to match your mobile carrier's APN settings
const char apn[] = "YOUR_CARRIER_APN";
const char gprsUser[] = "";
const char gprsPass[] = "";
// ========== SILICONWIT IO MQTT ==========
const char* mqtt_server = "mqtt.siliconwit.io";
const int mqtt_port = 8883;
const char* device_id = "YOUR_DEVICE_ID";
const char* access_token = "YOUR_ACCESS_TOKEN";
const char* pub_topic = "d/YOUR_DEVICE_ID/t";
// ========== COMMUNICATION STACK ==========
// SIM800C (plain TCP) → ESP32 (TLS) → MQTT Broker
HardwareSerial SerialAT(1);
TinyGsm modem(SerialAT);
TinyGsmClient gsmClient(modem);
ESP_SSLClient sslClient;
PubSubClient mqtt(sslClient);
// ========== TIMING ==========
unsigned long lastTelemetry = 0;
const unsigned long TELEMETRY_INTERVAL = 30000;
// ========== MODEM INIT ==========
bool initModem() {
Serial.println("[MODEM] Initializing...");
modem.restart();
delay(3000);
String modemInfo = modem.getModemInfo();
Serial.print("[MODEM] Info: ");
Serial.println(modemInfo);
int simStatus = modem.getSimStatus();
if (simStatus != 1) {
Serial.println("[MODEM] SIM not ready!");
return false;
}
Serial.println("[MODEM] SIM ready");
Serial.print("[MODEM] Waiting for network...");
if (!modem.waitForNetwork(60000)) {
Serial.println(" FAILED");
return false;
}
Serial.println(" OK");
Serial.print("[MODEM] Signal quality: ");
Serial.println(modem.getSignalQuality());
return true;
}
// ========== GPRS CONNECTION ==========
bool connectGPRS() {
Serial.print("[GPRS] Connecting to APN: ");
Serial.println(apn);
if (!modem.gprsConnect(apn, gprsUser, gprsPass)) {
Serial.println("[GPRS] Connection FAILED");
return false;
}
Serial.println("[GPRS] Connected!");
Serial.print("[GPRS] IP: ");
Serial.println(modem.localIP());
return true;
}
// ========== MQTT CONNECTION ==========
bool connectMQTT() {
Serial.print("[MQTT] Connecting to SiliconWit IO...");
if (mqtt.connect(device_id, device_id, access_token)) {
Serial.println(" connected!");
return true;
}
Serial.print(" failed, rc=");
Serial.println(mqtt.state());
return false;
}
// ========== SEND TELEMETRY ==========
void sendTelemetry() {
JsonDocument doc;
doc["signal"] = modem.getSignalQuality();
doc["uptime"] = millis() / 1000;
String payload;
serializeJson(doc, payload);
if (mqtt.publish(pub_topic, payload.c_str())) {
Serial.print("[TELEMETRY] Sent: ");
Serial.println(payload);
} else {
Serial.println("[TELEMETRY] Publish failed!");
}
}
// ========== SETUP ==========
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n=== ESP32 + SIM800C GPRS → SiliconWit IO ===\n");
// Initialize SIM800C UART at 9600 baud (default for SIM800C)
SerialAT.begin(9600, SERIAL_8N1, SIM800C_RX_PIN, SIM800C_TX_PIN);
delay(3000);
// Initialize modem and wait for network
if (!initModem()) {
Serial.println("[ERROR] Modem init failed. Check SIM800C wiring and SIM card.");
while (true) delay(1000);
}
// Connect to GPRS
if (!connectGPRS()) {
Serial.println("[ERROR] GPRS failed. Check APN settings.");
while (true) delay(1000);
}
// TLS handled by ESP32 (SIM800C only does plain TCP)
sslClient.setClient(&gsmClient);
sslClient.setInsecure();
// Configure MQTT
mqtt.setServer(mqtt_server, mqtt_port);
mqtt.setBufferSize(512);
// Connect to MQTT broker with retries
int retries = 0;
while (!connectMQTT() && retries < 5) {
retries++;
delay(5000);
}
if (!mqtt.connected()) {
Serial.println("[ERROR] MQTT connection failed after 5 retries.");
while (true) delay(1000);
}
// Send initial telemetry
sendTelemetry();
lastTelemetry = millis();
}
// ========== LOOP ==========
void loop() {
// Reconnect GPRS if disconnected
if (!modem.isGprsConnected()) {
Serial.println("[GPRS] Disconnected, reconnecting...");
connectGPRS();
}
// Reconnect MQTT if disconnected
if (!mqtt.connected()) {
Serial.println("[MQTT] Disconnected, reconnecting...");
connectMQTT();
}
// Keep MQTT alive
mqtt.loop();
// Send telemetry at regular intervals
if (millis() - lastTelemetry >= TELEMETRY_INTERVAL) {
lastTelemetry = millis();
sendTelemetry();
}
}

TinyGSM provides an Arduino Client-compatible interface for GSM modems. It handles all the AT commands internally:

#define TINY_GSM_MODEM_SIM800 // Tell TinyGSM which modem we're using
#define TINY_GSM_RX_BUFFER 1024 // Increase receive buffer for MQTT packets
HardwareSerial SerialAT(1); // Use UART1 for modem communication
TinyGsm modem(SerialAT); // Create modem instance
TinyGsmClient gsmClient(modem); // Create TCP client from modem

The HardwareSerial(1) uses UART1 on the ESP32, leaving UART0 (or USB CDC) free for debug serial output.

Since the SIM800C cannot handle TLS 1.2, we use the ESP_SSLClient library to perform TLS on the ESP32:

ESP_SSLClient sslClient;
sslClient.setClient(&gsmClient); // Wrap the plain TCP client with TLS
sslClient.setInsecure(); // Skip certificate verification

This creates a layered stack: gsmClient (plain TCP via SIM800C) is wrapped by sslClient (TLS via ESP32), which is then used by PubSubClient (MQTT).

The connection sequence is:

  1. modem.restart() — Reset the SIM800C module
  2. modem.getSimStatus() — Verify a SIM card is inserted and ready
  3. modem.waitForNetwork() — Wait for the module to register on the cellular network
  4. modem.gprsConnect(apn, user, pass) — Establish a GPRS data session
  5. mqtt.connect() — Connect to the MQTT broker over the GPRS link

The firmware publishes a JSON payload every 30 seconds:

{
"signal": 15,
"uptime": 120
}
FieldTypeDescription
signalintegerGSM signal quality (0–31, higher is better; 99 = unknown)
uptimeintegerSeconds since the ESP32 booted

You can add any custom data fields. For example, with a temperature sensor:

doc["temperature"] = readTemperature();
doc["humidity"] = readHumidity();
doc["battery_voltage"] = readBatteryVoltage();
ValueSignal StrengthDescription
0–9MarginalMay have connection issues
10–14OKUsable for data
15–19GoodReliable for MQTT
20–31ExcellentBest performance
99UnknownNot detectable
Terminal window
pio run --target upload

If upload fails:

  1. Hold the BOOT button
  2. Press and release the RST button
  3. Release the BOOT button
  4. Run the upload command immediately
Terminal window
pio device monitor

Expected output on successful connection:

=== ESP32 + SIM800C GPRS → SiliconWit IO ===
[MODEM] Initializing...
[MODEM] Info: SIM800 R14.18
[MODEM] SIM ready
[MODEM] Waiting for network... OK
[MODEM] Signal quality: 18
[GPRS] Connecting to APN: safaricom
[GPRS] Connected!
[GPRS] IP: 10.214.xx.xx
[MQTT] Connecting to SiliconWit IO... connected!
[TELEMETRY] Sent: {"signal":18,"uptime":12}
  1. Log in to your SiliconWit IO dashboard.
  2. Navigate to your registered device.
  3. You should see telemetry data updating every 30 seconds.
  4. The dashboard displays signal strength and uptime, and any additional sensor fields you add.
SymptomCauseSolution
”[MODEM] SIM not ready!”SIM card not detectedRe-seat the SIM card. Check orientation.
”Waiting for network… FAILED”No 2G coverage or SIM not activatedTry a different location. Verify 2G is available with your carrier.
Modem returns garbage charactersWrong baud rateTry 115200 instead of 9600, or auto-detect with modem.setBaud(9600).
Modem doesn’t respond at allWiring or power issueCheck TX/RX connections (they should be crossed). Verify power supply.
SymptomCauseSolution
”[GPRS] Connection FAILED”Wrong APNVerify APN with your carrier. Try with empty username/password.
GPRS connects then dropsWeak signal or carrier issueMove to better coverage. Increase reconnect delay.
No IP address assignedData plan not activeContact your carrier to ensure data is enabled on the SIM.
Symptommqtt.state()Solution
Connection timeout-2GPRS may be too slow. Increase connection timeout. Check signal strength.
TLS handshake fails-2Ensure sslClient.setInsecure() is called before connecting.
Authentication failed4 or 5Verify device_id and access_token match your SiliconWit IO dashboard.
Publishes fail after a whileGPRS connection dropped silently. The auto-reconnect in loop() should handle this.
SymptomCauseSolution
SIM800C resets during transmissionInsufficient currentUse a LiPo battery or dedicated 4V/2A supply.
Voltage drops below 3.4V under loadPower supply too weakAdd a large capacitor (1000–2200μF) near VCC/GND.
ESP32 resets when SIM800C transmitsShared power supply overloadedUse separate power supplies for ESP32 and SIM800C.
  • Publishing telemetry data at intervals of 30 seconds or more
  • Small JSON payloads (under 256 bytes)
  • Infrequent connections (connect, send, disconnect pattern)
  • MQTT Subscribe — The persistent connection required for receiving messages is unreliable over 2G due to high latency and frequent keep-alive failures.
  • Real-time control — Round-trip latency on 2G GPRS is typically 500ms–2000ms, making it unsuitable for real-time relay control.
  • Large payloads — GPRS bandwidth is limited to ~50–80 kbps. Keep payloads small.
  • Frequent publishes — Publishing more often than every 10 seconds may overwhelm the slow connection.
Use CaseIntervalNotes
Weather station5–15 minutesTemperature, humidity, pressure change slowly
Asset tracking1–5 minutesGPS location updates
Soil moisture15–30 minutesChanges very slowly
General monitoring30 secondsDefault in this tutorial
Power-constrained15–60 minutesMaximizes battery life

For battery-powered deployments, consider using deep sleep between transmissions:

#define uS_TO_S_FACTOR 1000000
#define TIME_TO_SLEEP 300 // Sleep for 5 minutes
void setup() {
Serial.begin(115200);
// Initialize modem and connect
SerialAT.begin(9600, SERIAL_8N1, SIM800C_RX_PIN, SIM800C_TX_PIN);
delay(3000);
initModem();
connectGPRS();
sslClient.setClient(&gsmClient);
sslClient.setInsecure();
mqtt.setServer(mqtt_server, mqtt_port);
mqtt.setBufferSize(512);
if (mqtt.connect(device_id, device_id, access_token)) {
sendTelemetry();
delay(100);
mqtt.disconnect();
}
// Disconnect GPRS and put modem to sleep
modem.gprsDisconnect();
modem.sleepEnable();
// Put ESP32 into deep sleep
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
esp_deep_sleep_start();
}
void loop() {
// Never reached — ESP32 resets after deep sleep
}

This pattern dramatically reduces power consumption:

ModeCurrent DrawDuration
Active + transmitting~350mA (ESP32) + ~2A peak (SIM800C)~10 seconds
Deep sleep (ESP32) + SIM800C sleep~10μA (ESP32) + ~1mA (SIM800C)5 minutes
#include <DHT.h>
#define DHTPIN 15
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
void sendTelemetry() {
float temp = dht.readTemperature();
float hum = dht.readHumidity();
JsonDocument doc;
doc["signal"] = modem.getSignalQuality();
doc["uptime"] = millis() / 1000;
if (!isnan(temp)) doc["temperature"] = temp;
if (!isnan(hum)) doc["humidity"] = hum;
String payload;
serializeJson(doc, payload);
mqtt.publish(pub_topic, payload.c_str());
}

Example: GPS Location (if using A9G or GPS module)

Section titled “Example: GPS Location (if using A9G or GPS module)”
void sendTelemetry() {
float lat, lon;
modem.getGPS(&lat, &lon); // Only works with GPS-enabled modems
JsonDocument doc;
doc["signal"] = modem.getSignalQuality();
doc["latitude"] = lat;
doc["longitude"] = lon;
String payload;
serializeJson(doc, payload);
mqtt.publish(pub_topic, payload.c_str());
}
#define BATTERY_PIN 34 // ADC pin connected to voltage divider
void sendTelemetry() {
int raw = analogRead(BATTERY_PIN);
float voltage = (raw / 4095.0) * 3.3 * 2.0; // Assuming 1:1 voltage divider
JsonDocument doc;
doc["signal"] = modem.getSignalQuality();
doc["battery_v"] = voltage;
doc["uptime"] = millis() / 1000;
String payload;
serializeJson(doc, payload);
mqtt.publish(pub_topic, payload.c_str());
}

SiliconWit IO automatically detects and displays new data fields on the dashboard.

In this tutorial, you built a cellular IoT telemetry system using:

  • ESP32 as the main controller
  • SIM800C for 2G GPRS cellular connectivity
  • TinyGSM for modem communication
  • ESP_SSLClient for TLS encryption (handled by ESP32, not SIM800C)
  • PubSubClient for MQTT messaging
  • SiliconWit IO as the cloud platform

This setup is ideal for remote IoT deployments where WiFi is not available. The key insight is that TLS must be handled by the ESP32 because the SIM800C only supports TLS 1.0, while modern MQTT brokers require TLS 1.2.

For bidirectional control (sending commands to the device), use WiFi instead of 2G GPRS, as described in the companion tutorial: ESP32 WiFi MQTT Relay Control.