Introdução à Sincronização de Tempo em Sistemas Embarcados
Em ambientes de robótica e sisetmas embarcados, a precisão da sincronização de tempo é um requisito fundamental. A acurácia temporal garante que os dados de sensores, atuadores e outros componentes do sistema sejam correlacionados corretamente, evitando a tomada de decisões baseadas em informações desatualizadas. Por exemplo, a utilização de dados de sensores antigos para controle de movimento pode levar a comportamentos imprevisíveis ou falhas de segurança.
O framework micro-ROS, projetado para dispositivos com recursos limitados, oferece um conjunto de APIs robustas para gerenciar e sincronizar o tempo entre o dispositivo embarcado e um nó ROS 2. Este artigo detalha o processo de implementar a sincronização de tempo usando micro-ROS em uma placa ESP32, demonstrando a funcionalidade ao exibir um relógio sincronizado em um display OLED.
Configuração do Ambiente de Desenvolvimento
Criação do Projeto
Começamos criando um novo projeto no PlatformIO. Para este exemplo, nomearemos o projeto como example15_time_sync. Este projeto servirá como base para integrar as bibliotecas necessárias e o código de sincronização.
Gerenciamento de Dependências
O projeto requer três bibliotecas principais: a implementação do micro-ROS para PlatformIO, a biblioteca do driver para o display OLED SSD1306 da Adafruit e a biblioteca Time, que facilita a manipulação de informações de data e hora. As seguintes linhas devem ser adicionadas ao arquivo platformio.ini na seção lib_deps:
[env:featheresp32]
platform = espressif32
board = featheresp32
framework = arduino
lib_deps =
https://gitee.com/ohhuo/micro_ros_platformio.git
adafruit/Adafruit SSD1306@^2.5.7
paulstoffregen/Time@^1.6.1
Implementação do Firmware
O firmware para o ESP32 é composto por um único arquivo que gerencia a comunicação micro-ROS, a sincronização de tempo e a exibição no OLED. Abaixo está o código cmopleto, com comentários para clareza:
#include <Arduino.h>
#include <micro_ros_platformio.h>
#include <rcl/rcl.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>
#include <TimeLib.h> // Biblioteca para manipulação de tempo (setTime, year, month, etc.)
#include <Adafruit_GFX.h> // Biblioteca gráfica para displays Adafruit
#include <Adafruit_SSD1306.h> // Driver específico para display SSD1306
// Declaração do objeto para o display OLED
Adafruit_SSD1306 oled_display(128, 64, &Wire);
// Variáveis globais para micro-ROS
rclc_executor_t rcl_executor;
rclc_support_t rc_support;
rcl_allocator_t mem_allocator;
rcl_node_t uros_node;
// Constantes e variáveis para o gerenciamento de tempo
const int SYNC_TIMEOUT_MS = 1000; // Tempo limite para a sessão de sincronização em milissegundos
static int64_t current_epoch_ms; // Tempo de época em milissegundos
static time_t current_unix_time; // Tempo Unix em segundos
char formatted_time_buffer[25]; // Buffer para a string de tempo formatada
// Offset de tempo para um fuso horário específico (ex: UTC+8 para Horário de Pequim)
const long TIME_OFFSET_SECONDS = 8 * 3600;
void setup()
{
Serial.begin(115200);
// Configura a porta serial para comunicação micro-ROS
set_microros_serial_transports(Serial);
delay(2000); // Aguarda a inicialização da comunicação serial
// Inicializa o alocador de memória padrão do ROS 2
mem_allocator = rcl_get_default_allocator();
// Inicializa a estrutura de suporte do ROS 2
rclc_support_init(&rc_support, 0, NULL, &mem_allocator);
// Cria um nó ROS 2 com o nome "example15_time_sync"
rclc_node_init_default(&uros_node, "example15_time_sync", "", &rc_support);
// Inicializa o executor ROS 2
rclc_executor_init(&rcl_executor, &rc_support.context, 1, &mem_allocator);
// Inicializa a comunicação I2C para o display OLED
Wire.begin(18, 19);
oled_display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Endereço I2C padrão do OLED
oled_display.setTextSize(2); // Define o tamanho da fonte
oled_display.clearDisplay(); // Limpa o buffer do display
oled_display.setCursor(0, 0); // Define a posição inicial do cursor
oled_display.setTextColor(SSD1306_WHITE); // Define a cor do texto
oled_display.println("Iniciando..."); // Mensagem inicial no display
oled_display.display();
}
void loop()
{
/*========================= Lógica de Sincronização de Tempo =====================================*/
while (!rmw_uros_epoch_synchronized()) // Verifica se o tempo já está sincronizado
{
rmw_uros_sync_session(SYNC_TIMEOUT_MS); // Tenta sincronizar a sessão de tempo
if (rmw_uros_epoch_synchronized())
{
current_epoch_ms = rmw_uros_epoch_millis(); // Obtém o tempo de época atual em ms
current_unix_time = current_epoch_ms / 1000; // Converte para segundos (tempo Unix)
// Define o tempo do sistema com o offset do fuso horário
setTime(current_unix_time + TIME_OFFSET_SECONDS);
}
delay(10); // Pequeno atraso antes de tentar novamente
return; // Retorna para o início do loop para re-verificar a sincronização
}
/*======================== Obtenção e Exibição do Tempo ==================================*/
// Formata a string de tempo usando as funções da biblioteca TimeLib
sprintf(formatted_time_buffer, "%04d-%02d-%02d %02d:%02d:%02d",
year(), month(), day(), hour(), minute(), second());
oled_display.clearDisplay(); // Limpa o display para a nova exibição
oled_display.setCursor(00, 0); // Define a posição do cursor no display
oled_display.println(formatted_time_buffer); // Escreve a string de tempo formatada
oled_display.display(); // Atualiza o display OLED
delay(100); // Pequeno atraso para taxa de atualização
}
APIs Essenciais para Sincronização de Tempo
O coração da funcionalidade de sincronização de tempo no micro-ROS reside em um pequeno conjunto de funções. São elas:
rmw_uros_epoch_synchronized(): Esta função booleana verifica se o dispositivo micro-ROS estabeleceu uma sincronização de tempo bem-sucedida com o agente ROS 2. Retornatruese a sincronização foi concluída, efalsecaso contrário.rmw_uros_sync_session(timeout_ms): Inicia ou continua uma tentativa de sincronização da sessão de tempo. Ela tenta obter o tempo de época do agente ROS 2. O parâmetrotimeout_msespecifica o tempo máximo em milissegundos que a função deve esperar pela resposta.rmw_uros_epoch_millis(): Após a sincronização ser estabelecida, esta função retorna o tempo de época atual em milissegundos. Este valor representa o número de milissegundos desde a "época" Unix (1º de janeiro de 1970, 00:00:00 UTC), ajustado de acordo com o tempo sincronizado do agente.
Teste e Verificação
Compilação e Upload
Após a implementação do código, o firmware deve ser compilado e carregado na placa ESP32. Utilize o comando de compilação e upload do PlatformIO:
platformio run --target upload
Inicialização do Agente micro-ROS
Para que o dispositivo micro-ROS possa se comunicar e sincronizar o tempo, um agente micro-ROS precisa estar em execução no host. Inicie o agente usando o Docker, especificando a porta serial à qual o ESP32 está conectado (substitua /dev/ttyUSB0 pela sua porta real):
sudo docker run -it --rm -v /dev:/dev -v /dev/shm:/dev/shm --privileged --net=host microros/micro-ros-agent:$ROS_DISTRO serial --dev /dev/ttyUSB0 -v
Após iniciar o agente e reiniciar o ESP32 (pressionando o botão RST), o dispositivo tentará se conectar e sincronizar o tempo.
Verificação da Conectividade e Tempo
Você pode verificar a conectividade do nó micro-ROS com o sistema ROS 2 executando o seguinte comando em um novo terminal:
ros2 node list
Deverá ser listado o nó /example15_time_sync. Adicionalmente, você pode verificar os dados do tópico /clock (se o sistema ROS 2 estiver usando simulação de tempo) ou simplesmente observar a saída no display OLED do ESP32 para confirmar que o relógio está exibindo o tempo corretamente sincronizado.