Assistentes de IA de Bancada — Movimento: cabeça robótica do assistente com servos e driver PCA9685 via I2C

Ouvir

Carregando voz…

Assistentes de IA de Bancada — Movimento: cabeça robótica do assistente com servos e driver PCA9685 via I2C

(a) Conceito e Arquitetura

Um assistente de bancada que apenas fala e escreve na tela é um terminal com voz. O movimento é o que transforma o conjunto em presença: o usuário entende “estou te ouvindo”, “não entendi” ou “preciso da sua atenção” antes de ler qualquer palavra. Este post cobre a camada de atuação desse assistente — o pescoço pan/tilt e, opcionalmente, braços, orelhas e antenas — usando servos de modelismo comandados por um driver PCA9685 via barramento I2C.

A arquitetura tem três camadas bem separadas: percepção (microfone I2S e câmera — assunto do próximo post), decisão (ESP32-S3 ou um host na rede) e atuação (PWM). O ponto central é que o microcontrolador não gera os 50 Hz de cada servo em software. Ele envia comandos de 12 bits para o PCA9685, que mantém 16 canais PWM estáveis de forma autônoma enquanto o firmware cuida de áudio, rede e inferência. Um único barramento de dois fios (SDA/SCL) endereça até 62 dessas placas (0x40 a 0x7F, selecionados pelos jumpers A0–A5), o que permite escalar de um pescoço de dois eixos para um torso completo sem consumir mais GPIOs.

Fluxo elétrico: uma fonte de 5–6 V alimenta exclusivamente a trilha V+ do driver; a lógica da placa (VCC) fica nos 3,3 V do MCU; o terra é único e comum a tudo. Cada canal entrega o pulso que o servo espera — em geral 500 a 2500 µs a 50 Hz, com o curso mecânico real entre 1 ms e 2 ms para 0°–180°, variando por modelo. Comando por I2C leva cerca de 1 ms para 16 canais a 400 kHz, então atualizar 60 vezes por segundo o pescoço inteiro é perfeitamente viável.

(b) BOM — Lista de Materiais

Os itens marcados como “crítico” são os que aparecem em 90% dos projetos que falham por alimentação.

Item Qtd Especificação Observação
MCU 1 ESP32-S3 DevKit (N16R8) ou ESP32 clássico 3,3 V de lógica; I2C em 400 kHz
Driver PWM 1 Módulo PCA9685, 16 canais, 12 bits Endereço padrão 0x40; VCC 3,3 V
Servo pan 1 MG996R (metal) ou DS3218 (20 kg·cm) Maior curso e torque no eixo horizontal
Servo tilt 1 MG90S ou SG90 (metal/plástico) Carga menor, mais rápido
Servos extras 0–4 SG90 para orelhas, braços, antenas Movimento secundário, baixo torque
Fonte 1 Chaveada 5 V / 10 A (ou 6 V / 8 A) CRÍTICO: não use USB do PC
Capacitor 1 Eletrolítico 1000 µF / 16 V + 100 nF cerâmico CRÍTICO: junto ao V+ do driver
Fios AWG 20–22 para V+ e GND; AWG 24–26 para sinal CRÍTICO: fio fino causa queda de tensão
Barramento de terra 1 Borne ou placa de distribuição Ponto estrela único
Mecânica Suporte 3D, rolamentos 608ZZ no eixo pan Evita folga e esforço do servo
Proteção 1 Fusível rearmável 5 A na linha V+ Trava mecânica pode virar curto

(c) Wiring & Esquemático

O PCA9685 tem quatro grupos de pinos: alimentação lógica, barramento I2C, saídas PWM e o pino OE (output enable, ativo em nível baixo). A pinagem abaixo é a que funciona sem surpresas no ESP32-S3; no ESP32 clássico, os pinos I2C padrão são GPIO21 (SDA) e GPIO22 (SCL).

Pino (PCA9685) Destino Função / Observação
VCC 3V3 do ESP32-S3 Lógica da placa. Nunca 5 V: o I2C subiria para 5 V e queimaria o GPIO
GND Terra comum (ponto estrela) Obrigatório unir GND do MCU e GND da fonte de servos
SDA GPIO8 (S3) / GPIO21 (ESP32) Pull-up 4,7 kΩ para 3V3 se o módulo não tiver
SCL GPIO9 (S3) / GPIO22 (ESP32) Idem; mantenha o par junto com um fio de GND
OE GND (ou GPIO livre) Ativo baixo: ligado a GND habilita as saídas; via GPIO permite desligar todos os servos
V+ +5 V da fonte dedicada Trilha de potência. Não compartilhe com o USB nem com o VCC
Canal 0–15 Sinal do servo (fio laranja/branco) Cada servo vai à sua própria fonte: SIG no canal, +V e GND no borne da fonte

Alimentação separada dos servos. O rail V+ deve vir da fonte chaveada, não do regulador da placa. O servo MG996R pode puxar 2,5 A no stall e o DS3218 passa de 3 A; quatro servos mal comportados somam mais de 10 A por alguns milissegundos. O regulador do DevKit entrega, no melhor caso, 1 A e o USB do PC, 500 mA.

Pico de corrente. Coloque o eletrolítico de 1000 µF o mais perto possível dos bornes V+ e GND do driver, e um cerâmico de 100 nF em paralelo para ruído de alta frequência. O capacitor não “aumenta” a fonte: ele absorve o transiente de partida e impede que a tensão caia abaixo do limiar do brownout.

Terra comum. Um único ponto de terra (estrela), com o fio de potência e o de lógica convergindo nele. Ligar o GND do servo em cascata por outro servo cria diferença de potencial e jitter.

Cuidados mecânicos. Antes de energizar, mova o pescoço à mão em todo o curso e confirme que não há batente mecânico dentro da faixa que o software vai usar. Travar um servo contra o próprio limite é a forma mais rápida de queimar o driver.

(d) Código — varredura, easing e limites de curso

A biblioteca oficial da Adafruit traz setPWM(canal, 0, ticks), em que ticks é o valor de 12 bits (0–4095) dentro do período de 50 Hz. A conversão de microssegundos para ticks é us × 4096 / 20000.

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40);

const uint16_t TICKS   = 4096;    // resolucao de 12 bits
const uint16_t PERIODO = 20000;   // 20 ms @ 50 Hz
const uint16_t US_MIN  = 500;     // pulso minimo do servo
const uint16_t US_MAX  = 2500;    // pulso maximo do servo

uint16_t usParaTicks(uint16_t us) {
  return (uint32_t)us * TICKS / PERIODO;
}

uint16_t anguloParaUs(float graus, float gMin, float gMax) {
  graus = constrain(graus, gMin, gMax);
  return US_MIN + (US_MAX - US_MIN) * (graus - gMin) / (gMax - gMin);
}

void setup() {
  Wire.begin(8, 9);                        // SDA, SCL no ESP32-S3
  Wire.setClock(400000);                   // 400 kHz
  pwm.begin();
  pwm.setOscillatorFrequency(27000000.0);  // calibre com a sua placa
  pwm.setPWMFreq(50);                      // 50 Hz para servo analogico
}

// interpola com easing cubico respeitando os limites mecanicos
void moveEixo(uint8_t ch, float &atual, float destino,
              int16_t gMin, int16_t gMax,
              uint16_t passoMs, uint16_t duracaoMs) {
  destino = constrain(destino, (float)gMin, (float)gMax);
  uint16_t passos = max(1, (int)(duracaoMs / passoMs));
  float inicio = atual;

  for (uint16_t i = 1; i <= passos; i++) {
    float t = (float)i / passos;
    float e = (t < 0.5) ? 4*t*t*t : 1 - pow(-2*t + 2, 3) / 2;
    atual = inicio + (destino - inicio) * e;
    pwm.setPWM(ch, 0, usParaTicks(anguloParaUs(atual, gMin, gMax)));
    delay(passoMs);
  }
}

void loop() {
  static float pan = 90, tilt = 90;

  moveEixo(0, pan,  130, 20, 160, 15, 700);   // olha para a direita
  moveEixo(1, tilt, 110, 35, 145, 15, 500);
  delay(400);
  moveEixo(0, pan,   50, 20, 160, 15, 700);   // varredura de bancada
  moveEixo(1, tilt,  80, 35, 145, 15, 500);
  delay(400);
  moveEixo(0, pan,   90, 20, 160, 15, 600);   // retorna ao centro
  moveEixo(1, tilt,  90, 35, 145, 15, 600);
  delay(1500);
}

Três decisões importam mais que o resto. Limites de curso: os parâmetros gMin/gMax (20°–160° no pan, 35°–145° no tilt) impedem que o firmware mande o servo além do batente — o constrain é a última linha de defesa, não a primeira. Suavização: interpolar em passos de 15 ms com curva easeInOutCubic elimina o tranco que aparece quando se escreve o ângulo final direto; é isso que faz o movimento parecer “intencional” em vez de mecânico. Não bloqueante: o delay() acima serve para ilustrar; em produção, troque por agendamento com micros() e mova os eixos em paralelo, para que o assistente continue respondendo enquanto se move.

(e) Troubleshooting

Sintoma Causa provável Correção
ESP32 reinicia quando o servo arranca Brownout: rail compartilhado com o MCU e queda de tensão Fonte separada para V+, terra comum, 1000 µF no driver, fio AWG 20
Reinício persiste só com um servo Servo em stall puxando pico alto Alivie a carga mecânica ou troque por servo de maior torque
Jitter / tremor contínuo Rampa de I2C longa, falta de pull-up, servo barato Cabo I2C curto (<30 cm) e trançado, pull-up 4,7 kΩ, cerâmico 100 nF
Servo esquenta parado Pulso fora do range do modelo Ajuste US_MIN/US_MAX e calibre o oscilador do PCA9685
Torque insuficiente / engasga Queda de tensão no fio ou servo subdimensionado AWG 20, fonte 5 A+, 6 V se o servo suportar, rolamento no eixo
Endereço I2C não encontrado Jumpers A0–A5 ou VCC errado Rode um scanner I2C e confira o endereço esperado (0x40)

O reset por brownout é o sintoma mais comum e o mais mal diagnosticado. Desabilitar o detector (WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0)) só esconde o problema: a tensão ainda está caindo e o comportamento dos servos vai ficar errático. Corrija a alimentação. Já o jitter de alguns graus normalmente não vem do servo, e sim do barramento: I2C e PWM convivem mal quando o terra é compartilhado em série. Por fim, torque insuficiente quase nunca é culpa do servo antes de ser culpa do fio de 1 mm² que faz 0,4 V de queda em 3 A.

(f) Aplicações reais — o que o movimento comunica

Movimento é linguagem. Um assistente que inclina a cabeça quando está processando, faz um “não” curto ao rejeitar um comando e varre a bancada quando está em idle comunica estado sem gastar uma palavra. Na prática, os padrões que funcionam melhor são poucos: idle scan (varredura lenta de 30° a 60° com pausas), gaze (pan/tilt apontando para a pessoa detectada pela câmera), acknowledge (inclinação de 5° e retorno), negação (dois movimentos curtos no eixo pan) e alerta (subida rápida do tilt, seguida de congelamento).

Além da interação, a mesma arquitetura serve para funções utilitárias: gimbal do sensor de visão (fechando o loop com o post da câmera), posicionamento de uma câmera de inspeção em bancada de eletrônica, animatrônica e vitrines, telepresença com espelhamento de gestos e dispositivos de acessibilidade em que a cabeça robótica sinaliza para pessoas com baixa visão. Em todos os casos, o gargalo não é o servo: é a fonte e o terra.

Não some depois da matéria

Radar do hub: Núcleo Hits, Além do Oculto, Bobinho. Sem spray, sem lista comprada.

Radar do hub

Um e-mail quando sair matéria que importa — sem spray de newsletter genérica.