Desbravando o PyBricks: Programando o SPIKE Prime em Python do Zero
Aula

Desafio: Criar um robô que parte da base, segue uma linha e para diante de um obstáculo com segurança e repetibilidade.

Objetivos de aprendizagem

  • Integrar DriveBase, ColorSensor e UltrasonicSensor num mesmo programa.
  • Aplicar controle proporcional (P) para seguir linha.
  • Implementar parada por distância com margem de segurança.
  • Organizar o código em funções reutilizáveis (equivalentes a MyBlocks com entrada/saída).
  • (Opcional) Usar giroscópio para manter rumo em trechos de linha “desbotada”.

Requisitos do cenário

  • Uma linha preta sobre base clara (ou o inverso) partindo da base.
  • Um obstáculo posicionado sobre a linha (caixinha/parede), a ~200–300 mm do ponto de parada desejado.
  • Altura do ultrassônico alinhada e sensor de cor a 2–3 mm do piso.

Arquitetura do código

Dividiremos em funções (como MyBlocks):

  • configurar_robot() – Inicializa hub, motores, sensores e DriveBase.
  • seguir_linha_P(alvo, kp, v) – Mantém o robô sobre a borda da linha usando controle P (entrada: alvo/kp/v; sem retorno).
  • distancia_media(n) – Retorna média das leituras do ultrassônico (entrada: janela n; saída: distância em mm).
  • ir_da_base_ate_obstaculo(...) – Comanda o fluxo: parte da base, segue linha e para ao atingir a distância alvo.

Parâmetros de calibração

  • alvo_reflexao: média entre claro e escuro (meça no seu tapete).
  • Kp_linha: ganho do controlador P do segue-linha (comece entre 3.0 e 4.5).
  • vel_mm_s: velocidade linear alvo (150–220 mm/s para precisão).
  • dist_parada_mm: distância de parada do obstáculo (ex.: 200 mm).

Código completo do projeto

Compatível com SPIKE Prime + PyBricks. Ajuste portas e dimensões do robô.

from pybricks.hubs import PrimeHub
from pybricks.pupdevices import Motor, ColorSensor, UltrasonicSensor
from pybricks.robotics import DriveBase
from pybricks.parameters import Port, Color, Stop
from pybricks.tools import wait

# ---------------------------
# Configurações (ajuste ao seu robô)
# ---------------------------
WHEEL_DIAMETER = 56   # mm
AXLE_TRACK    = 120  # mm (distância entre rodas, centro a centro)

ALVO_REFLEXAO = 50   # média entre claro/escuro
KP_LINHA      = 4.0  # ganho do P para seguir linha
VEL_MM_S      = 180  # velocidade linear

DIST_PARADA_MM = 200 # para a ~200 mm do obstáculo
JANELA_MEDIA   = 4   # tamanho da média móvel para ultrassônico

USAR_GIROSCOPIO = True  # opcional: correção de rumo
KP_RUMO        = 2.0 # ganho do P para rumo (heading)

# ---------------------------
# Inicialização
# ---------------------------
hub = PrimeHub()
hub.imu.reset_heading(0)

mE = Motor(Port.A)   # motor esquerdo
mD = Motor(Port.B)   # motor direito
sensor_cor = ColorSensor(Port.C)
us         = UltrasonicSensor(Port.D)

robot = DriveBase(mE, mD, WHEEL_DIAMETER, AXLE_TRACK)
robot.settings(straight_speed=VEL_MM_S, turn_rate=90)

# ---------------------------
# Funções utilitárias (MyBlocks em Python)
# ---------------------------
def distancia_media(n=JANELA_MEDIA):
    global _buffer
    try:
        _buffer
    except NameError:
        _buffer = [us.distance()] * n
    _buffer.pop(0)
    _buffer.append(us.distance())
    return sum(_buffer) / len(_buffer)

def seguir_linha_P(alvo=ALVO_REFLEXAO, kp=KP_LINHA, v=VEL_MM_S, usar_imu=USAR_GIROSCOPIO, kp_rumo=KP_RUMO):
    ref = sensor_cor.reflection()
    erro_linha = alvo - ref
    turn = kp * erro_linha              # correção de curva (°/s)

    if usar_imu:
        rumo_erro = 0 - hub.imu.heading()  # manter heading ~ 0
        turn += kp_rumo * rumo_erro        # mistura leve de correção por rumo

    robot.drive(v, turn)

def ir_da_base_ate_obstaculo(dist_parada=DIST_PARADA_MM):
    while True:
        d = distancia_media(JANELA_MEDIA)
        if d <= dist_parada:
            robot.stop(Stop.HOLD)
            hub.light.on(Color.YELLOW)
            break
        seguir_linha_P()
        wait(15)

# ---------------------------
# Fluxo principal
# ---------------------------
def main():
    hub.light.on(Color.BLUE)     # pronto para sair da base
    wait(300)
    hub.light.off()

    ir_da_base_ate_obstaculo(DIST_PARADA_MM)

    hub.light.on(Color.GREEN)    # objetivo cumprido
    wait(500)

main()

Explicando entradas (inputs) e saídas (outputs)

  • seguir_linha_P(alvo, kp, v, usar_imu, kp_rumo)entradas: alvo de reflexão, ganho P, velocidade e opções de correção por IMU; saída: controla o robô (sem retorno).
  • distancia_media(n)entrada: tamanho da janela; saída: distância filtrada em mm (valor retornado por return).
  • ir_da_base_ate_obstaculo(dist_parada)entrada: distância alvo de parada; saída: nenhuma (efeito é parar o robô).
Equivalência com MyBlocks: cada função acima se comporta como um MyBlock:
• Parâmetros = entradas do bloco  •  return (quando existe) = saída do bloco.

Calibração passo a passo

  1. Reflexão: meça claro e escuro com sensor_cor.reflection(); defina ALVO_REFLEXAO como a média.
  2. Kp linha: comece com 3.0. Se o robô “sai” da borda, aumente; se oscila, diminua.
  3. Velocidade: inicie em 160–200 mm/s para estabilidade; aumente só após ficar consistente.
  4. Ultrassônico: valide a leitura em mm e ajuste DIST_PARADA_MM conforme sua mesa/obstáculo.
  5. IMU (opcional): use hub.imu.reset_heading(0) antes de iniciar e ajuste KP_RUMO (1.5–2.5).

Rubrica de avaliação (FLL)

Critério Básico Bom Excelente
Repetibilidade Completa 1x 3/5 execuções 5/5 execuções
Precisão da parada ±40 mm ±20 mm ±10 mm
Organização do código Sem funções Funções básicas Funções modulares e parâmetros claros
Calibração e testes Poucos ajustes Ajustes por sessão Registro de medições e evolução de Kp/vel

Checklist rápido

  • Leituras de reflexão calibradas e estáveis (altura do sensor).
  • Ultrassônico sem interferências (suporte firme, sem peças à frente).
  • Rodas bem fixas e mesma bitola; cabos sem puxar o chassi.
  • Heading resetado no início; piso limpo e nível.
  • Velocidade adequada à precisão desejada.

Erros comuns e correções

  • Oscilação na linha: reduza KP_LINHA ou VEL_MM_S; ajuste ALVO_REFLEXAO.
  • Parada irregular: use distancia_media() (média móvel) e garanta obstáculo plano.
  • Puxa para um lado: verifique montagem, diâmetro de roda e axle_track; considere leve correção por IMU.
  • “Perde” a linha: aproxime o sensor do chão (2–3 mm), use borda mais marcada e diminua a velocidade.

Extensões (para ir além)

  • Waypoints de cor: mude parâmetros (vel/Kp) ao detectar marcadores coloridos.
  • Retomada de rota: se perder a linha, execute varredura em arco curto até reencontrar.
  • Estacionamento: após parar no obstáculo, recuar 50 mm e alinhar por giro preciso (IMU).
  • Modularização: separar funções em arquivos: linha.py, distancia.py, missoes.py.

Parabéns! Com este projeto, sua equipe domina o essencial para unir percepção (sensores) e ação (DriveBase), criando trajetórias confiáveis na FLL.