Sincronização de Tempo com micro-ROS em Sistemas Embarcados e Display OLED

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. Retorna true se a sincronização foi concluída, e false caso 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âmetro timeout_ms especifica 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.

Tags: micro-ROS ROS2 ESP32 PlatformIO OLED

Publicado em 9-7 01:59