CATEGORIA

Conversando com o SIM7600G no Arduino Mega — do “AT” ao JSON

Este artigo mostra como utilizar as placas de desenvolvimento Arduino Mega 2560 e um modem SIM7600G em um nó IoT celular capaz de coletar dados de rede, GPS e status do SIM, empacotá-los em JSON, que futuramente enviará para qualquer back-end.

Read more: Conversando com o SIM7600G no Arduino Mega — do “AT” ao JSON

Por que um Mega 2560 + SIM7600G?

  • SRAM abundante (8 kB) — gera JSON, mantém buffers e ainda sobra para lógica de aplicação; um UNO ficaria no limite.
  • UART dedicada (Serial1) — o Mega oferece portas seriais extras, isolando o tráfego do modem do console USB.
  • SIM7600G — cobre 2G/3G/4G e GNSS no mesmo chip, simplificando o hardware.

A pinagem utilizada para este projeto está explicada neste post

Estrutura do projeto

arduino-mobile-sensor/
├─ src/                // sketch principal
├─ include/            // headers próprios
├─ lib/                // bibliotecas internas
└─ .pio/               // cache do PlatformIO

https://gitlab.com/mfs.eng.br/hardware-quirks/arduino-sim7600-telemetry

Anatomia do código em C++

Inclusões mínimas

#include <Arduino.h>        // HAL AVR
#include <avr/pgmspace.h>   // PROGMEM
#include <ArduinoJson.h>    // Única lib externa

Definições constantes

constexpr uint32_t USB_BAUD   = 115200;
constexpr uint32_t MODEM_BAUD = 115200;
constexpr uint16_t TIMEOUT_MS = 1500;
#define OUTPUTDEBUG 0

/* ------------ buffer de recepção ----------------------------- */
static char   respBuf[160];
static size_t respLen = 0;
  • constexpr garante avaliação em compile-time;
  • Flags #define ativam logs sem custo quando desligadas;
  • Buffers globais (char respBuf[160]) vivem em BSS, evitando uso de heap.

Strings AT em Flash

/* ------------ comandos em flash ------------------------------ */
const char CMD_CGSN[]    PROGMEM = "AT+CGSN";
const char CMD_CIMI[]    PROGMEM = "AT+CIMI";
const char CMD_CGMM[]    PROGMEM = "AT+CGMM";
const char CMD_CSPN[]    PROGMEM = "AT+CSPN?";
const char CMD_CSQ[]     PROGMEM = "AT+CSQ";
const char CMD_CREG[]    PROGMEM = "AT+CREG?";
const char CMD_COPS[]    PROGMEM = "AT+COPS?";
const char CMD_CGPADDR[] PROGMEM = "AT+CGPADDR";
const char CMD_CGDCONT[] PROGMEM = "AT+CGDCONT?";
const char AT_GPSINFO[]PROGMEM = "AT+CGPSINFO";

PROGMEM poupa SRAM; cada comando moraria no Flash.

Compatibilidade Flash-print

#ifndef FPSTR
  #define FPSTR(p) (reinterpret_cast<const __FlashStringHelper *>(p))
#endif

Permite Serial.println() imprimir strings guardadas em Flash.

Máquina de leitura/escrita dos comandos AT (sendAT)

Envio

Serial1.println(cmd); // já inclui \r\n

Coleta em bloco

// Loop principal: fica ativo enquanto o tempo decorrido for menor que 'tout'
while (millis() - t0 < tout)
{
    // Loop de leitura: executa enquanto houver bytes no UART *e*
    // ainda houver espaço no buffer (deixa 1 byte livre para o '\0')
    while (Serial1.available() && respLen < sizeof(respBuf) - 1)
        // Lê um byte da porta Serial1 e armazena na posição atual do buffer,
        // depois incrementa 'respLen' para apontar para o próximo espaço livre
        respBuf[respLen++] = Serial1.read();

    // Coloca terminador nulo na posição atual, transformando respBuf
    // em uma string C válida (necessário para usar funções de <string.h>)
    respBuf[respLen] = '\0';

    // Verifica se a resposta já traz um "\nOK" ou "\nERROR"
    // — padrões que sinalizam fim de resposta AT.
    // Se encontrar, sai imediatamente do loop externo (timeout não é mais necessário)
    if (strstr(respBuf, "\nOK") || strstr(respBuf, "\nERROR"))
        break;
}

A função singleLineAT

/* ---------- devolve primeira linha “útil” -------------------- */
bool singleLineAT(const __FlashStringHelper *cmd, char *dst, size_t dstSz)
{
  if (!sendAT(cmd)) return false;

  char *p = respBuf;
  while (*p) {
    /* pula CR/LF */
    while (*p == '\r' || *p == '\n') ++p;
    if (!*p) break;
    /* encontra fim da linha */
    char *q = p;
    while (*q && *q != '\r' && *q != '\n') ++q;

    /* copia linha para tmp */
    char line[128];
    size_t len = min((size_t)(q - p), sizeof line - 1);
    memcpy(line, p, len); line[len] = '\0';

    /* trim início */
    char *s = line;
    while (*s == ' ') ++s;

    /* descarta eco, OK, ERROR */
    if (!strncmp(s, "AT", 2) || !strcmp(s, "OK") || !strncmp(s, "ERROR", 5)) {
      p = q;
      continue;
    }

    /* achou payload */
    strncpy(dst, s, dstSz - 1);
    dst[dstSz - 1] = '\0';
    return true;
  }
  return false;
}

A rotina singleLineAT() encapsula todo o trabalho de disparar um comando AT e devolver exatamente a primeira linha útil da resposta, já limpa de ecos, quebras de linha, espaços extras e tokens de controle.

  1. Disparo e verificação de sucesso if (!sendAT(cmd)) return false; Reaproveita sendAT() para enviar o comando e encher respBuf. Se ocorrer ERROR ou timeout, a função já devolve false.
  2. Iterador p percorre o buffer bruto char *p = respBuf; while (*p) { … } Varre o buffer byte a byte até achar uma linha que valha a pena.
  3. Pular quebras de linha while (*p == '\r' || *p == '\n') ++p; Garante que p sempre aponte para o primeiro caractere “real” da linha corrente.
  4. Encontrar o fim da linha corrente char *q = p; while (*q && *q != '\r' && *q != '\n') ++q; q corre até \r ou \n, definindo o intervalo [p, q) que compõe a linha.
  5. Copiar para buffer temporário char line[128]; size_t len = min((size_t)(q - p), sizeof line - 1); memcpy(line, p, len); line[len] = '\0'; Faz uma cópia local para manipular sem afetar respBuf, limitando o tamanho para evitar overflow.
  6. Copiar o payload ao destino strncpy(dst, s, dstSz - 1); dst[dstSz - 1] = '\0'; return true; Quando encontra uma linha que não se encaixa nos descartes, transfere-a para o buffer do chamador (dst) e termina a função.
  7. Se nenhuma linha útil aparecer
    O laço termina naturalmente e devolve false; o chamador sabe que não há payload para processar.

Boas práticas de embarcados — buffers fixos em vez de String

BenefícioConsequência no campo
Zero fragmentação de heapUptime sem resets imprevistos
Tempo de execução determinísticoSem pausas aleatórias de realloc

Funções de conversão e limpeza

FunçãoPropósito
ddmmToDecimal()NO GPS converte coordenadas NMEA ddmm.mmm → graus decimais
csqToDbm()0-31/99 → dBm (-113 a -51)
cregText()Código 0-5 → texto “registered (roaming)”
techText()0/1/2/7/8 → “2G/3G/4G/CDMA”
csvField()Extrai campo n de “CMD: a,b,c”
stripQuotes()Remove aspas e espaços nas extremidades

Todas recebem ponteiros/valores primitivos para otimização de memória.

Roteiro

  1. Setup
    • Abre USB e UART do modem.
    • Habilita GPS automático (AT+CGPSAUTO=1).
    • Define exibição numérica de operadora (AT+COPS=3,2).
  2. Loop
    • Se chegar “INFO” pelo USB, dispara sendInfoJSON() e imprime o JSON.
    • Caso contrário, funciona como serial transparente PC ↔ modem.

sendInfoJSON() executa a sequência dos comandos AT, converte métricas e monta o JSON final.

Campos gerados e valor para telecom

CampoUso prático
imeiInventário de ativos, id do dispositivo (modem)
imsiInventário de ativos, id do simcard
spnNome comercial da operadora
csq / rssiDiagnóstico de antena e qualidade de cobertura
reg / ranEstado de registro e tecnologia (2G/3G/4G)
ip / apnValidação de contexto de dados (PDP/Bearer)
locationGeorreferenciamento

O JSON completo cabe em ~300 bytes, ideal para MQTT

Tricks and Tips

Reaproveitar respBuf — o mesmo buffer serve à detecção de fim de comando e à análise da resposta, evitando tráfego duplicado.

  • singleLineAT() universal — limpa eco e ruído em qualquer comando que retorne uma única linha.
  • Strings em Flash — elimina 300+ bytes de SRAM (crítico em AVR).

Próximos passos sugeridos

  1. Publicar via MQTT/TLS
  2. Gerenciar energia — desligar GPS (AT+CGPS=0) fora das janelas de leitura.
  3. FOTA update de firmware remoto direto ao módulo.

Conclusão

Esse é o primeiro passo: entender o diálogo “AT” e convertê-lo em dados de negócio. No próximo artigo, veremos como transmitir essa telemetria de forma segura e escalável para a nuvem.

Índice
    Add a header to begin generating the table of contents

    Type & Enter to Search