Sistema Autopiloto Avançado para Barcos à Vela – Direção Automática de Precisão
Componentes e Materiais
- 1x Arduino UNO (MCU principal)
- 1x Arduino Nano R3 (Watchdog independente)
- 1x GPS Beitian BN-220T (configurado 2Hz, apenas sentença $GNRMC)
- 1x Motor de Passo 23LM (54 passos = 1/4 volta)
- 1x Driver Keyes L298
- 1x Controle Remoto RF 433MHz XD-YK04 (4 botões + antena de fio)
- 6x Botões normalmente abertos (2 vermelhos, 2 verdes, 1 preto, 1 azul)
- 1x Chave liga/desliga (branca)
- Conectores redondos 6 pinos macho/fêmea (motor externo)
- 1x Buzzer
- 1x Display LCD1602 2x16 + conversor I2C
- 3x LEDs (vermelho, azul, amarelo)
- 1x EEPROM I2C 24C04 (512 bytes)
- 1x Multiplexador 4051
- 1x Bateria LiPo 2S 7.4V 2600mAh
- 1x Regulador 7805 + dissipador
- 1x Termistor NTC MF52-103 10k
- 1x Fusível ressetável 2A
- 6x Diodos 1N4148
- Resistores e capacitores conforme lista
- Headers, jumpers, caixa transparente "à prova d'água"
Sobre o Projeto
Prefácio
Adoro velejar sozinho: no mar, com seu barco, você tem tudo para evoluir. Tempo ruim é duro, mas dias de sol e vento bom são o máximo. Felicidade: horizontes infinitos, técnica perfeita, escolhas ótimas... e um bom drink com sanduíche! Exatamente aí entra o Autopiloto: trabalha por você enquanto você toma seu chá das 5 no mar. :-)
O que o Autopiloto faz por você
Barco à vela não tem motor e não segue rota programada sozinho. Todo trabalho é do velejador: ajustar velas, monitorar vento/clima, manusear cabos, vigiar tráfego, decidir rumo e timonear. Quando o velejador quer pausa (10 seg a alguns min – o famoso "hora do chá"), liga o autopiloto. Em segundos, GPS adquire posição, velocidade e rumo, mantendo a direção. O sistema de direção (pau de leme conectado ao leme), antes movido pelas mãos do velejador, passa a ser controlado pelo autopiloto via motor de passo com polias e cabos.
Controlar o leme é trabalho contínuo de ajuste fino ou grosso. Barcos menores/leves sofrem mais influências: ondas, vento, deslocamento de peso a bordo, correntes. Mas o velejador fica atento, ajustando rota via controle remoto: 4 botões (+1, -1, +10, -10 graus). Esses botões também estão no autopiloto (verde/direita, vermelho/esquerda). Botão azul (central) = ativar/pausar. Botão preto = configurar parâmetros na memória.

O Circuito
Processamento principal: Arduino Uno. Arduino Nano = watchdog externo (sei que Uno tem watchdog interno, mas quis microcontrolador independente – sonho realizado!). Uno alimenta Nano via pino 3 → A0 (HIGH/LOW, 5V/0V) pelo menos 1 vez a cada 2.5s (feedingInterval); se não, Uno "travou" → Nano reseta Uno. Nunca aconteceu ainda!
Display LCD1602 + conversor I2C (4 fios, economiza pinos). Botões e controle remoto via divisores de tensão resistivos (menos portas do MCU). Usei resistores 1% precisão; valores analógicos no código. Se botões não reconhecidos por resistores diferentes, ajuste constantes em checkRfRC() e checkHWButtons().
RF 433MHz funciona bem; adicionei antena de bobina (fio de cobre) → testado 10m, acho que chega a 20m+ (barco teste: 4.20m).
GPS: Comecei com EM406A, mas sofria Week-Rollover-Bug (muito antigo). Troquei por Beitian BN-220T. Configure via software para saída 2Hz apenas sentença $GNRMC. GPS TX → pino 0 (RX) do Uno. Desconecte GPS temporariamente durante upload de sketch!
EEPROM 24C04 (512 bytes I2C) para gravar parâmetros do motor de passo (explicados em Software).
Lista de Componentes Detalhada
(Ver lista completa na seção Componentes acima)
Sensores no multiplexador 4051:
- Termistor – temperatura do dissipador do 7805
- Resistor 2W 0.22Ω + 4x10k (divisores) – cálculo de consumo (Amperes)
- Tensão da bateria LiPo – crítica abaixo de 3.3V/célula. Pacote 2S → alarme se < 7.0V (bips rápidos). Desligue e recarregue logo!
LEDs: Amarelo pisca 1Hz = watchdog ok; Azul = autopiloto ativo (apagado = pausa); Vermelho pisca = botão RC pressionado.
Tudo a 5.0V via LiPo 2S 7.4V 2600mAh + 7805. Corrente < 800mA (típico 100-450mA). Dissipador no 7805 obrigatório! Termistor no dissipador → buzzer apita se > 50°C.








PCBs e Montagem
PCBs face única → alguns jumpers (linhas pontilhadas) para resolver rotas. Mostradas faces de componentes; arquivos para download: faces componentes, solda, espelhadas – impressão laser em folhas "amarelas" ou "azuis" (azuis dizem ser melhores, mas mais caras). Usei amarelas. Desative economia de toner, use 1200 dpi para preto profundo. Transferência de toner com ferro quente. Impressão nas duas faces facilita posicionamento e dá ar profissional.
Ambas PCBs dimensionadas para empilhar sobre o Arduino Uno: Power Shield embaixo, Autopilot Shield por cima.
Optei por não soldar tudo junto: usei headers e jumpers Dupont (~200 conexões não soldadas). Permite reuso, mas pode causar mau contato. Sugestão: soldar tudo para circuito estável!


Configuração de Parâmetros e Valores dos Sensores
Botão preto entra no Modo Setup (pode durante navegação ativa, sem pausar). Pressionar repetidamente navega pelas páginas:
- Sensores: Tensão bateria (V=7.83), Consumo (mA=177), Temp. dissipador (38°C)
- Interval: ex. 2000 ms – tempo entre tentativas do motor de passo para restaurar rumo
- Minimum: ex. 2° – desvio mínimo para autopiloto intervir (até isso, leme centralizado)
- Max: ex. 40° – máximo giro do motor por vez (se cálculo der 50°, move só 40°)
- Coeffic.: ex. 1.50x – coeficiente de giro (cálculo 40° → motor move 40×1.50=60°)
- Página 6: "Updating..." se algo mudou → salva na EEPROM
Parâmetros essenciais para ajuste fino no barco: responsividade, suavidade dependem de diâmetro das polias, qtd. polias, sensibilidade do leme, comprimento do pau de leme, etc. Instale, teste em dia bonito com vento fraco!
Funcionamento "Ao Vivo"
Navegando no mar/lago/porto. Hora do chá: ligue autopiloto, aguarde fix GPS. Display mostra velocidade (nós), relógio, rumo atual H270° (R=rota alvo, H=rumo real). Lembre: 180°=S, 270°=W, 360/0°=N, 90°=E. R e H iguais em Pausa (mostra STOP). Conecte cabo do motor ao pau de leme, pressione azul → autopiloto assume direção; mantém R e corrige H (-10°, +5°, etc. até H=R). Ajuste rota com botões vermelho/verde na unidade ou controle remoto. Para reassumir: pausa (botão azul), desconecte cabo, timoneie manual. Pronto.
Lado do Software
Código longo, mas claro. Usa ~65% programa, ~45% memória. Mesmo com classe String (manipulação NMEA), fluxo estável. Usa serialEvent() para receber GPS 2x/seg, chama nmeaExtractData(), valida com nmea0183_checksum(). Se GPS diferente, verifique estrutura da sentença (ex: EM406A usa $GPRMC, BN-220 usa $GNRMC – só mudar nome). Link útil checksum: nmeachecksum.eqth.net.
Exemplo sentença NMEA completa:
$GPRMC,095836.000,A,4551.9676,N,01328.7118,E,2.09,341.84,280519,,*08
No Setup(): EEPROM verificada – se nova/desconhecida, inicializada (formatada). Parâmetros lidos/escritos como bytes: endereços 0=0x29, 1=0x00, 2-3=interval, 4-5=min, 6-7=max, 8-11=coefficient (byte, byte, int, int, float). Operações EEPROM defensivas. Sensores checados a cada 10s via readMuxSensors() (multiplexador) → alarme se bateria baixa ou temp. alta. Resolução consumo ~40mA. Botões HW/RC checados continuamente; comportamento depende de IsSetup e RefreshDisplay(). Núcleo: seção STEERING CONTROL chama gomotor() para mover motor out/back (leme 10° direita, após interval volta a zero, novo cálculo). Direção funciona até no Setup. Watchdog feeding simples: toggle pino ASAP.
Instalação no Barco à Vela
Conforme foto: autopiloto e motor de passo na popa, bem fixos com parafusos. Cabo 6mm da polia principal passa por duas polias laterais fixadas ao barco com anéis de elástico (mantém tensão leve). Decida como conectar cabo ao pau de leme (conexão temporária; fácil conectar/desconectar). Mantenha sistema longe da água! :-)

Novidades e Atualizações
- 10.05.2020: arquivos .STEP 3D CAD para polia do motor (autor) e placa de montagem (Andrew Barney), + preview 3D.
Aviso Legal e Alertas
Isto é um jogo, nada sério! Anos atrás fiz viagem de 16 meses ao redor do mundo em barco à vela. Navegamos extensivamente com autopiloto REAL (NÃO ESTE!) em todas condições, até mau tempo. Autopiloto real é hardware/software MUITO ROBUSTO no qual se confia muito. Este autopiloto Arduino é um jogo fantástico para brincar e passar tempo divertido.
Divirta-se!
Marco Zonca
Código
Sketch Autopiloto (para Uno)
/* This sketch act as Autopilot for small sailing boats, by Marco Zonca, 2019
Arduino UNO as CPU, Arduino Nano as watchdog, GPS BT-220 nmea, stepper motor + controller,
rf433Mhz RC, 6 buttons, buzzer, i2c display, 2 leds, i2c 24c04 eeprom, Mux 4051 for sensors,
lipo 2s 7.4v 2600mA, 7805 voltage regulator, Thermistor; */
#include <LiquidCrystal_I2C.h>
#include <NewTone.h>
#include <Stepper.h>
#include <Wire.h>
String inputString = "";
String nm_time = "00:00:00";
String nm_validity = "V";
String nm_latitude = "ddmm.mmmm'N";
String nm_longitude = "dddmm.mmmm'E";
String nm_knots = "0.0kn";
float nmf_knots = 0.0;
String nm_truecourse = "360";
float nmf_truecourse = 360;
String nm_date = "dd/mm/yyyy";
String nm_routetofollow = "000";
float nmf_routetofollow = 0;
unsigned long previousStearingMillis = 0;
unsigned long currentStearingMillis = 0;
unsigned long prevCheckSensorsMillis = 0;
unsigned long currCheckSensorsMillis = 0;
int CheckSensorsInterval = 10000;
bool stringComplete = false;
bool isfirstfix = true;
bool ispause = true;
bool isStearing = false;
bool isSetup = false;
int s=0, y=0, z=0, d=0;
int rfRemoteControlValue = 0;
int HWButtonValue = 0;
int SetupParameter = 0;
float calcmove = 0, cm = 0, Stearing = 0, prevStearing = 0, t = 0;
int EEdisk = 0x50, EEid1 = 0x29, EEid2 = 0x00;
unsigned int EEaddress = 0, EEbytes = 12;
byte EEdata[12], EEbytedata;
int EEerr = 0;
float SensorVBatt=0, SensorVRes=0, SensorTemp=0, SensormAmp=0;
// Parâmetros default (lidos/gravados na EEPROM)
// endereço len tipo notas
// 0-255 bytes em 0x50 EEdisk, 256-512 em 0x51 (não usado)
// 0 1B byte 01001001 (0x29 id1)
// 1 1B byte 00000000 (0x00 id2)
int StearingInterval = 2000; // ms entre tentativas 2-3B int 1000-5000 step 100
int StearingMinToMove = 2; // graus 4-5B int 0-20 step 1
int StearingMaxMove = 40; // graus 6-7B int 10-45 step 1
float StearingCoeffMove = 1.5; // (graus * coeff) 8-11B float 0.1-4 step 0.1
// 12 livre
byte bStearingInterval[sizeof(int)];
byte bStearingMinToMove[sizeof(int)];
byte bStearingMaxMove[sizeof(int)];
byte bStearingCoeffMove[sizeof(float)];
int prev_StearingInterval=0, prev_StearingMinToMove=0, prev_StearingMaxMove=0;
float prev_StearingCoeffMove=0;
const int ledpausePin = 2;
const int watchDogPin = 3;
const int MuxSelBit0Pin = 7; // 00=Vin 01=Vbatt 10=Temp
const int MuxSelBit1Pin = 6;
const int motorsABenablePin = 13;
const int MuxIOPin = 14;
const int ButtonsPin = 15;
const int rfRemoteControlPin = 16;
const int speakerPin = 17;
const int RCleftbutton = 201, RCrightbutton = 202, RCleft10button = 203, RCright10button = 204;
const int HWleftbutton = 101, HWrightbutton = 102, HWpausebutton = 103, HWsetupbutton = 104;
const int HWleft10button = 105, HWright10button = 106;
const int motorStepsPerRevolution = 200; // 200 para modelo 23LM
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
Stepper motor(motorStepsPerRevolution, 9, 10, 11, 12);
void setup() {
Serial.begin(4800);
lcd.begin(16,2);
Wire.begin();
motor.setSpeed(60);
inputString.reserve(200);
pinMode(motorsABenablePin, OUTPUT);
pinMode(MuxSelBit0Pin, OUTPUT);
pinMode(MuxSelBit1Pin, OUTPUT);
digitalWrite(motorsABenablePin, LOW);
digitalWrite(MuxSelBit0Pin, LOW);
digitalWrite(MuxSelBit1Pin, LOW);
pinMode(ledpausePin, OUTPUT);
pinMode(watchDogPin, OUTPUT);
digitalWrite(ledpausePin, LOW);
digitalWrite(watchDogPin, LOW);
// read+check EEPROM (formatting if new)
lcd.clear(); lcd.setCursor(0,0); lcd.print("Memory check...");
lcd.setCursor(0,1);
for (s = 0; s < EEbytes; s++) {
EEaddress = s; EEbytedata = readEEPROM(EEdisk, EEaddress); EEdata[s] = EEbytedata;
}
if (EEerr) { lcd.print("E="); lcd.print(EEerr); delay(1000); }
if ((EEdata[0] != EEid1) || (EEdata[1] != EEid2)) {
lcd.print(" init!"); goupdateEEPROM();
if (EEerr) { lcd.print("E="); lcd.print(EEerr); delay(1000); }
}
memcpy(bStearingInterval, EEdata+2, sizeof(int));
memcpy(bStearingMinToMove, EEdata+4, sizeof(int));
memcpy(bStearingMaxMove, EEdata+6, sizeof(int));
memcpy(bStearingCoeffMove, EEdata+8, sizeof(float));
StearingInterval = *((int*)bStearingInterval);
StearingMinToMove = *((int*)bStearingMinToMove);
StearingMaxMove = *((int*)bStearingMaxMove);
StearingCoeffMove = *((float*)bStearingCoeffMove);
prev_StearingInterval = StearingInterval;
prev_StearingMinToMove = StearingMinToMove;
prev_StearingMaxMove = StearingMaxMove;
prev_StearingCoeffMove = StearingCoeffMove;
lcd.print(" OK"); delay(1000);
lcd.clear(); lcd.print("GPS reading..."); delay(1000);
}
void loop() {
// CHECK SENSORS
currCheckSensorsMillis = millis();
if (currCheckSensorsMillis - prevCheckSensorsMillis >= CheckSensorsInterval) {
readMuxSensors();
if ((SensorVBatt <= 7.0) || (SensorTemp >= 50)) {
lcd.clear(); lcd.setCursor(0,0); lcd.print("Alarm sensors! ");
lcd.setCursor(1,1); lcd.print("V="); lcd.print(SensorVBatt); lcd.print(" ");
lcd.print(int(SensorTemp)); lcd.write(0xDF); lcd.print("C");
NewTone(speakerPin,10); delay(1000); noNewTone();
}
prevCheckSensorsMillis = currCheckSensorsMillis;
}
// STEERING CONTROL
currentStearingMillis = millis();
if (currentStearingMillis - previousStearingMillis >= StearingInterval) {
if (isStearing == false && ispause == false) {
calcmove = nmf_routetofollow - nmf_truecourse;
if (calcmove < -180) calcmove += 360;
else if (calcmove > 180) calcmove -= 360;
if (abs(calcmove) >= StearingMinToMove) {
if (abs(calcmove) >= StearingMaxMove) {
cm = (calcmove < 0) ? -StearingMaxMove : StearingMaxMove;
calcmove = cm;
}
Stearing = calcmove * StearingCoeffMove;
gomotor(int((Stearing * 216) / 360)); // 54 passos = 1/4 volta
prevStearing = Stearing; isStearing = true;
}
} else {
if (isStearing == true) {
Stearing = -prevStearing;
gomotor(int((Stearing * 216) / 360));
Stearing = 0; prevStearing = 0; isStearing = false;
}
}
previousStearingMillis = currentStearingMillis;
}
// RC RF BUTTONS
rfRemoteControlValue = checkRfRC();
if (rfRemoteControlValue) {
switch (rfRemoteControlValue) {
case RCleftbutton: goleft(); break;
case RCrightbutton: goright(); break;
case RCleft10button: goleft10(); break;
case RCright10button: goright10(); break;
}
}
// HW BUTTONS
HWButtonValue = checkHWButtons();
if (HWButtonValue) {
switch (HWButtonValue) {
case HWleftbutton: if (!isSetup) goleft(); else setupMinus(); break;
case HWrightbutton: if (!isSetup) goright(); else setupPlus(); break;
case HWpausebutton: gopause(); break;
case HWsetupbutton: gosetup(); break;
case HWleft10button: goleft10(); break;
case HWright10button: goright10(); break;
}
}
// GPS NMEA
if (stringComplete) {
bool ret = nmeaExtractData();
inputString = ""; stringComplete = false;
if (ret) RefreshDisplay();
}
// WATCHDOG FEEDING
if (digitalRead(watchDogPin) == LOW) digitalWrite(watchDogPin, HIGH);
else digitalWrite(watchDogPin, LOW);
}
// ... (demais funções: readMuxSensors, nmeaExtractData, setupPlus, setupMinus, gomotor,
// RefreshDisplay, serialEvent, nmea0183_checksum, checkRfRC, checkHWButtons, gosetup,
// goupdateEEPROM, goleft, goleft10, goright, goright10, gopause, readEEPROM, writeEEPROM,
// roundZeroDec, roundOneDec, roundTwoDec – código completo no GitHub)
Sketch Watchdog (para Nano)
/* Watchdog para manter CLIENTE (Uno) sob controle, Arduino NANO 3.0 by Marco Zonca
CLIENTE deve alimentar Watchdog antes de feedingInterval senão força restart */
const int feedingPin = 14;
const int ledPin = 15;
const int restartPin = 16;
const int buzzerPin = 17;
const long ledInterval = 1000;
const long feedingInterval = 2500;
const long timeForClientStart = 16000;
int ledState = LOW, previousFeedingState = LOW, feedingState = LOW;
unsigned long previousLedMillis = 0, previousFeedingMillis = 0;
void setup() {
digitalWrite(restartPin, HIGH); // LOW força restart do CLIENTE
pinMode(ledPin, OUTPUT);
pinMode(buzzerPin, OUTPUT);
pinMode(restartPin, OUTPUT);
pinMode(feedingPin, INPUT);
delay(timeForClientStart); // tempo para CLIENTE iniciar
}
void loop() {
unsigned long currentMillis = millis();
// BLINK LED
if (currentMillis - previousLedMillis >= ledInterval) {
previousLedMillis = currentMillis;
ledState = (ledState == LOW) ? HIGH : LOW;
digitalWrite(ledPin, ledState);
}
// CHECK FEEDING
feedingState = digitalRead(feedingPin);
if (feedingState == HIGH) {
if (previousFeedingState == LOW) previousFeedingMillis = currentMillis;
previousFeedingState = HIGH;
} else previousFeedingState = LOW;
if (currentMillis - previousFeedingMillis > feedingInterval) {
// CLIENTE dormindo
ledState = HIGH; digitalWrite(ledPin, ledState);
tone(buzzerPin,1500); delay(500);
digitalWrite(restartPin, LOW); // restart CLIENTE
tone(buzzerPin,1500); delay(500);
digitalWrite(restartPin, HIGH);
tone(buzzerPin,1500); delay(timeForClientStart);
noTone(buzzerPin);
currentMillis = millis();
previousFeedingState = LOW;
previousFeedingMillis = currentMillis;
previousLedMillis = currentMillis;
}
}
Peças Personalizadas e Caixas
23lm-stepper-plate-v2_PlvJaff9Hl.step
23lm-stepper-pulley-56_UhsbaWbiBt.step
Esquemáticos
Processo de manufatura
- Construa um Sensor de Movimento Infravermelho DIY para Raspberry Pi – Guia Passo a Passo
- Projetando um Sistema de Recuperação por Paraquedas Balístico para UAVs
- Monitoramento Contínuo é a Escolha Certa para Suas Operações?
- ATI Industrial Automation: Líder em Trocadores Automáticos de Ferramentas para Robôs Industriais
- Sua Fábrica Está Pronta para IoT? Um Guia Prático
- Cortadores Poligonais de Precisão para Tornos Automáticos – Soluções de Corte Ajustáveis e Alto Desempenho
- Torno CNC R07 Cincom – Usinagem de Precisão de 7 mm para Peças Pequenas
- Sistema de Propulsão Hipersônica Revolucionário Permite Viagens Transcontinentais em Menos de 30 Minutos
- Como Transmissões Manual e Automática Transferem Potência: Visão Clara
- Guia Abrangente de Sistemas de Transmissão Automática