Cellular (GSM)

Cellular (GSM)

Open in ChatGPT
Ask ChatGPT about this page
Open in Claude
Ask Claude about this page

02 — Getting started

02 — Getting started

Words you will meet

Cellular has its own vocabulary, and the library uses it where there is no
plainer word. Each of these comes up again below.

AT command A line of text sent to the modem, beginning with AT. The modem answers with zero or more lines and then a final OK or ERROR.
SIM PIN A code that locks the SIM card. Most IoT SIMs have none; if yours does, gsm.pin("1234").
Registration The network accepting the module onto a tower. Roughly "bars on the phone". Needed for SMS and for data.
APN Access point name: which of the operator's gateways the data goes through. Comes from your SIM's documentation.
Bearer The data connection itself, also called a PDP context. Brought up after registration, using the APN. Having one means having an IP address.
URC Unsolicited result code: a line the modem sends without being asked, such as "an SMS arrived" or "the network went away".
Cat-1, LTE-M, NB-IoT Kinds of LTE radio. Cat-1 is ordinary LTE at modest speed; LTE-M (Cat-M1) and NB-IoT are low-power variants that not every network or SIM carries.

On a board

#include <ModBeeGsm.h>

GsmDevice gsm;

void setup() {
    Serial1.setRxBufferSize(2048);     // see "the buffer" below
    Serial1.begin(115200);

    gsm.apn("internet");               // whatever your SIM's operator wants
    gsm.begin(&Serial1, 115200, 2048);
}

void loop() {
    gsm.poll();                        // the only call that does work

    if (gsm.isReady()) {
        // sockets may be opened
    }
}

No module is named anywhere. The library sends ATI ("identify yourself")
and the driver that recognises the answer drives the module.

An HTTP request

The point of this is what is not in it — there is no HTTP code, because
GsmClient is an Arduino Client and ArduinoHttpClient already knows how to
speak HTTP over one.

#include <ModBeeGsm.h>
#include <ArduinoHttpClient.h>

GsmDevice  gsm;
GsmClient  net(gsm.sockets());
HttpClient http(net, "example.com", 80);
bool       fetched = false;

void setup() {
    Serial.begin(115200);
    Serial1.setRxBufferSize(2048);
    Serial1.begin(115200);
    gsm.apn("internet");
    gsm.begin(&Serial1, 115200, 2048);
}

void loop() {
    gsm.poll();
    if (gsm.isReady() && !fetched) {
        fetched = true;
        http.get("/");
        Serial.println(http.responseStatusCode());
        Serial.println(http.responseBody());
    }
}

PubSubClient, WebSockets and anything else written against Client work the
same way. See 05 — Sockets and clients.

On a host

The same source builds for Linux, macOS and Windows. GsmSerialTransport opens
a serial device directly: a USB modem, a hardware serial port, or a
pseudo-terminal, which looks exactly like a modem from the library's side.

GsmSerialTransport link;
link.configure("/dev/ttyUSB0", 115200);

GsmDevice gsm;
gsm.apn("internet");
gsm.begin(&link);

while (!gsm.isReady()) { gsm.poll(); }

GsmSocket* socket = gsm.openTcp("example.com", 80);
socket->begin();                       // starts the open and returns
while (socket->state() == GsmSocketState::Opening) { gsm.poll(); }

On a host, waiting in a loop like this is fine; on a board, the same checks
belong in loop(). examples/cpp/ has a complete program that brings the
modem up, opens a socket, sends a line and prints the reply:

cmake -S . -B build -DGSM_BUILD_EXAMPLES=ON && cmake --build build -j
./build/examples/cpp/socket_on_a_host /dev/ttyUSB0 example.com 80

The host and port are whatever TCP server you want to reach over the cellular
link; the modem's own network is on the far side, so a local address such as
127.0.0.1 means nothing to it.

Nothing about the library changes between the two. The state machines, the
drivers and the socket layer are the same code; only the transport differs.

The buffer

Raise your serial receive buffer. The default on most cores is 256 bytes,
which is about 22 ms of traffic at 115200.

Most modem wiring is three wires. RTS and CTS exist on nearly every module but
are very often not brought out, and on a USB modem there is no hardware
handshake at all — so the modem cannot be told to wait. Ask it for 1500
bytes and 1500 bytes arrive, as fast as the line carries them.

An overrun does not lose a reading. It desynchronises the AT stream,
because the bytes that went missing were part of a length-delimited payload,
and everything after them is read as the wrong thing until something times out.

The library never asks for more than the transport says it can hold — in fact
slightly less, because the payload arrives wrapped in a header and an OK that
land in the same buffer. So a small buffer is safe, just slower. A larger one
is simply faster.

Two separate things, and they must agree. setRxBufferSize() actually
makes the buffer bigger, and only the ESP cores have it — SAMD, STM32 and AVR
compile their size in and cannot be changed at run time. The third argument to
begin() is what you tell the library the buffer holds:

#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266)
    const size_t rxBuffer = 2048;
    MODEM_SERIAL.setRxBufferSize(rxBuffer);
#else
    const size_t rxBuffer = 256;      // whatever your core compiled in
#endif
    gsm.begin(&MODEM_SERIAL, 115200, rxBuffer);

Claiming more than you have is the one that breaks. Leave the argument out
entirely and it assumes 256, which is the pessimistic answer and always safe.

This is not speculative: TinyGSM's own HTTP example carries the warning in its
header comment.

What isReady() means

The modem is registered and a data bearer is up. Those are different
things: you can be registered with no data context at all, which is exactly
what "full bars, no internet" looks like.

isRegistered() a tower has accepted us
hasBearer() we have an IP address
isReady() both
Last updated 2 hours ago
Was this helpful?
Thanks!