Preparando a cJSON para uso em microcontroladores
A biblioteca cJSON é composta basicamente por dois arquivos principais:
cJSON.h
cJSON.c
Em um projeto embarcado simples, normalmente copiamos esses dois arquivos para uma pasta do firmware, por exemplo:
project/
├── main.c
├── cJSON/
│ ├── cJSON.c
│ └── cJSON.h
├── app/
│ ├── sensor_mock.c
│ ├── sensor_mock.h
│ ├── web_mock.c
│ └── web_mock.h
└── build/
Essa estrutura é genérica. Ela pode ser adaptada para STM32CubeIDE, ESP-IDF, PlatformIO, CMake, Makefile puro, Zephyr, FreeRTOS ou até um projeto bare-metal simples. O ponto importante é que o compilador consiga encontrar o arquivo cJSON.h e compilar o arquivo cJSON.c junto com o restante do projeto.
Em um projeto com gcc, por exemplo, a compilação poderia ser representada assim:
gcc main.c app/sensor_mock.c app/web_mock.c cJSON/cJSON.c -I./cJSON -I./app -o firmware_sim
Em um microcontrolador real, o comando não seria exatamente esse, pois dependeria do compilador cruzado, como arm-none-eabi-gcc, riscv-none-elf-gcc, xtensa-esp32-elf-gcc ou outro toolchain. Ainda assim, a lógica é a mesma: o arquivo cJSON.c precisa entrar no build e o caminho do cJSON.h precisa estar configurado nos includes.
A cJSON trabalha criando uma árvore de objetos em memória. Quando fazemos algo como:
cJSON *root = cJSON_CreateObject();
a biblioteca aloca memória dinamicamente para representar o objeto JSON. Depois, cada campo adicionado também pode exigir novas alocações internas. Isso é muito confortável em computadores, mas exige atenção em microcontroladores, porque a memória RAM é limitada e fragmentação de heap pode se tornar um problema.
Por isso, a regra prática é: crie o JSON, use o JSON e libere o JSON o mais rápido possível. A função principal para liberar uma estrutura criada pela cJSON é:
cJSON_Delete(root);
Se esquecermos essa chamada, teremos vazamento de memória. Em firmware que roda continuamente por dias ou meses, um pequeno vazamento repetido dentro de uma tarefa periódica pode derrubar todo o sistema.
A cJSON também pode gerar uma string textual a partir da árvore JSON. Isso normalmente é feito com:
char *json_string = cJSON_PrintUnformatted(root);
Essa função também aloca memória dinamicamente para armazenar o texto final. Depois de usar essa string, precisamos liberar a memória com:
free(json_string);
Portanto, um fluxo básico e seguro é:
Criar objeto JSON
Adicionar campos
Converter para string
Usar a string
Liberar a string
Liberar o objeto JSON
Em C, isso fica conceitualmente assim:
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
int main(void)
{
cJSON *root = cJSON_CreateObject();
if (root == NULL)
{
printf("Erro ao criar objeto JSON.\n");
return 1;
}
cJSON_AddStringToObject(root, "device_id", "mcu-001");
cJSON_AddNumberToObject(root, "temperature", 28.75);
cJSON_AddNumberToObject(root, "voltage", 3.29);
cJSON_AddNumberToObject(root, "uptime", 15240);
char *json_string = cJSON_PrintUnformatted(root);
if (json_string == NULL)
{
printf("Erro ao converter JSON para texto.\n");
cJSON_Delete(root);
return 1;
}
printf("JSON gerado: %s\n", json_string);
free(json_string);
cJSON_Delete(root);
return 0;
}
Esse exemplo ainda não simula sensores nem serviço web. Ele apenas mostra o ciclo mínimo de uso da biblioteca: criar, preencher, imprimir e liberar.
A saída esperada seria parecida com:
{"device_id":"mcu-001","temperature":28.75,"voltage":3.29,"uptime":15240}
Em microcontroladores, geralmente preferimos cJSON_PrintUnformatted() em vez de cJSON_Print(), porque a versão formatada adiciona espaços, quebras de linha e indentação. Isso facilita a leitura por humanos, mas aumenta o tamanho da mensagem transmitida. Para comunicação via UART, Wi-Fi, LoRa, BLE, Ethernet ou modem celular, bytes extras significam mais tempo de transmissão e, em alguns casos, mais consumo de energia.
Outro ponto importante é que o uso direto de printf() no exemplo é apenas didático. Em um firmware real, poderíamos substituir por:
uart_write(json_string);
ou:
http_post("/api/sensors", json_string);
ou ainda:
mqtt_publish("devices/mcu-001/data", json_string);
Neste tutorial, para manter o código independente de plataforma, vamos criar uma função mock chamada web_mock_post(). Ela vai fingir que envia o JSON para um servidor e vai retornar uma resposta também em JSON.