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 JSONPor 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
Conectando o SIM7600G ao Arduino Mega
Notas de bancada sobre a ligação do modem LTE SIM7600G ao Arduino Mega, com pinagem, sketch C++ completo e lista…
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.
- Disparo e verificação de sucesso
if (!sendAT(cmd)) return false;
ReaproveitasendAT()
para enviar o comando e encherrespBuf
. Se ocorrerERROR
ou timeout, a função já devolvefalse
. - Iterador
p
percorre o buffer brutochar *p = respBuf; while (*p) { … }
Varre o buffer byte a byte até achar uma linha que valha a pena. - Pular quebras de linha
while (*p == '\r' || *p == '\n') ++p;
Garante quep
sempre aponte para o primeiro caractere “real” da linha corrente. - 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. - 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 afetarrespBuf
, limitando o tamanho para evitar overflow. - 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. - Se nenhuma linha útil aparecer
O laço termina naturalmente e devolvefalse
; o chamador sabe que não há payload para processar.
Boas práticas de embarcados — buffers fixos em vez de String
Benefício | Consequência no campo |
---|---|
Zero fragmentação de heap | Uptime sem resets imprevistos |
Tempo de execução determinístico | Sem pausas aleatórias de realloc |
Funções de conversão e limpeza
Função | Propó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
- 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
).
- Loop
- Se chegar “INFO” pelo USB, dispara
sendInfoJSON()
e imprime o JSON. - Caso contrário, funciona como serial transparente PC ↔ modem.
- Se chegar “INFO” pelo USB, dispara
sendInfoJSON()
executa a sequência dos comandos AT, converte métricas e monta o JSON final.
Campos gerados e valor para telecom
Campo | Uso prático |
---|---|
imei | Inventário de ativos, id do dispositivo (modem) |
imsi | Inventário de ativos, id do simcard |
spn | Nome comercial da operadora |
csq / rssi | Diagnóstico de antena e qualidade de cobertura |
reg / ran | Estado de registro e tecnologia (2G/3G/4G) |
ip / apn | Validação de contexto de dados (PDP/Bearer) |
location | Georreferenciamento |
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
- Publicar via MQTT/TLS
- Gerenciar energia — desligar GPS (
AT+CGPS=0
) fora das janelas de leitura. - 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.