Slogan: Código vs. Gravidade
Resumo: Este protótipo didático estabiliza uma bolinha de pingue-pongue em uma gangorra 1D usando Arduino UNO, um sensor de distância Sharp GP2Y0A21 e um servo padrão. O Arduino mede a distância da bola, calcula o erro e aplica um controlador PID para inclinar a barra, mantendo a esfera em equilíbrio.
O código a seguir é baseado no tutorial do canal ELECTRONOOBS; use-o como referência inicial. Ajuste os ganhos Kp, Ki e Kd, o setpoint e o mapeamento conforme a sua mecânica. O firmware oficial e aprimorado que usamos no projeto está disponível em assets/code/index/pid_balance no repositório — esta página apresenta apenas o código de base de terceiros.
/* PID balance code with ping pong ball and distance sensor sharp 2y0a21
* by ELECTRONOOBS: https://www.youtube.com/channel/UCjiVhIvGmRZixSzupD0sS9Q
* Tutorial: http://electronoobs.com/eng_arduino_tut100.php
* Code: http://electronoobs.com/eng_arduino_tut100_code1.php
* Scheamtic: http://electronoobs.com/eng_arduino_tut100_sch1.php
* 3D parts: http://electronoobs.com/eng_arduino_tut100_stl1.php
*/
#include <Wire.h>
#include <Servo.h>
///////////////////////Inputs/outputs///////////////////////
int Analog_in = A0;
Servo myservo; // create servo object to control a servo, later attatched to D9
///////////////////////////////////////////////////////
////////////////////////Variables///////////////////////
int Read = 0;
float distance = 0.0;
float elapsedTime, time, timePrev; //Variables for time control
float distance_previous_error, distance_error;
int period = 50; //Refresh rate period of the loop is 50ms
///////////////////////////////////////////////////////
///////////////////PID constants///////////////////////
float kp=8; //Mine was 8
float ki=0.2; //Mine was 0.2
float kd=3100; //Mine was 3100
float distance_setpoint = 21; //Should be the distance from sensor to the middle of the bar in mm
float PID_p, PID_i, PID_d, PID_total;
///////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
myservo.attach(9); // attaches the servo on pin 9 to the servo object
myservo.write(125); //Put the servo at angle 125, so the balance is in the middle
pinMode(Analog_in,INPUT);
time = millis();
}
void loop() {
if (millis() > time+period)
{
time = millis();
distance = get_dist(100);
distance_error = distance_setpoint - distance;
PID_p = kp * distance_error;
float dist_difference = distance_error - distance_previous_error;
PID_d = kd*((distance_error - distance_previous_error)/period);
if(-3 < distance_error && distance_error < 3)
{
PID_i = PID_i + (ki * distance_error);
}
else
{
PID_i = 0;
}
PID_total = PID_p + PID_i + PID_d;
PID_total = map(PID_total, -150, 150, 0, 150);
if(PID_total < 20){PID_total = 20;}
if(PID_total > 160) {PID_total = 160; }
myservo.write(PID_total+30);
distance_previous_error = distance_error;
}
}
float get_dist(int n)
{
long sum=0;
for(int i=0;i<n;i++)
{
sum=sum+analogRead(Analog_in);
}
float adc=sum/n;
float distance_cm = 17569.7 * pow(adc, -1.2062);
return(distance_cm);
}
Dica: se a gangorra se inclinar para o lado errado, inverta o sinal (troque distance_setpoint - distance por distance - distance_setpoint) ou ajuste o lado mecânico do servo.
Além dos itens essenciais, listamos algumas peças opcionais que podem aprimorar a experiência, como fonte de 7 V (para motores mais robustos), buzzer e LED com resistores, e um capacitor extra para filtrar o sensor.
| Grupo | Item | Qtd. | Observações |
|---|---|---|---|
| Controle | Arduino UNO (ou compatível) | 1 | 16 MHz; USB para programação |
| Sensor | Sharp GP2Y0A21 (10–80 cm) | 1 | Apontado para a bola; fixar firme; cabo 3 vias |
| Atuação | Servo padrão (ex.: Futaba S3003) | 1 | 4,8–6 V; torque típico 3–4 kg·cm (para 1D é suficiente) |
| Energia | Fonte 5 V externa (≥ 2 A) | 1 | Não alimente servo pela USB |
| Fonte 7 V (opcional) | 1 | Para quem deseja explorar servos de maior torque; use com regulador adequado | |
| Capacitor 100–470 µF (5 V) | 1 | Próximo ao servo (anti-ruído) e outro opcional junto ao sensor para filtrar picos | |
| Conexões | Protoboard + jumpers | — | GND comum entre Arduino e fonte dos servos; inclua resistores (220–330 Ω) para LED e buzzer |
| LED (opcional) | 1 | Indicador visual de centragem; usar com resistor | |
| Buzzer (opcional) | 1 | Alarme sonoro para quando a bola estiver equilibrada; usar com resistor | |
| Bola | Bolinha de pingue-pongue | 1 | Superfície fosca ajuda o IR |
| Estrutura (3D / laser) | Base inferior | 1 | Chapa 200×200 mm (MDF 3 mm / acrílico) |
| Suporte do servo (3D) | 1 | Suporte em L com furação padrão do servo | |
| Braço / berço da gangorra (3D) | 1 | Peça central que apoia a barra; encaixe no pivô | |
| Pivô / fulcro (3D ou parafuso + bucha) | 1 | Pode usar parafuso M4 + porcas/arruelas; opcionalmente rolamento 608 | |
| Barra da gangorra | 1 | 200–250 mm de comprimento; canal raso central para a bola | |
| Suporte do Sharp (3D) | 1 | Mantém o sensor apontado e na altura correta | |
| Adaptador para horn (3D) | 1 | Acopla o horn do servo à barra (ou bieleta curta) | |
| Espaçadores / colunas (3D) | 4 | Fixação entre base e nível da barra | |
| Abas / fixadores (3D) | 2–4 | Para prender cabos/sensor na base | |
| Protoboard (opcional) | 1 | Facilita a montagem provisória; inclua resistores para LED e buzzer |
As peças impressas em 3D correspondem ao que se vê no protótipo: suporte do servo, berço/pivô da barra, suporte do sensor, adaptador para horn e espaçadores. Todos os arquivos STL estão em assets/files/3dFiles do repositório.
Para orientar futuras montagens, a tabela abaixo resume três cenários: básico, usado no protótipo e recomendado. Assim fica fácil visualizar a evolução do projeto e escolher componentes conforme o seu orçamento e objetivo.
| Componente | Básico (baixo custo) | Usado no protótipo | Recomendado (melhorado) |
|---|---|---|---|
| Sensor de distância | Sharp GP2Y0A21 (10–80 cm) — sujeito a ruídos e saturação | Sharp GP2Y0A21 (10–80 cm) | Sharp GP2Y0A02 (8–110 cm) ou sensor time-of-flight (VL53L0X/VL53L1X) — maior alcance e estabilidade |
| Servo | Servos de baixo custo (analógicos, engrenagens plásticas) — podem não aguentar acrílico ou torque; em alguns casos, substitua a base por isopor para reduzir o peso | Futaba S3003 | Servos de maior torque com engrenagens metálicas (MG996R, DS3218) — melhor precisão |
| Placa controladora | Arduino UNO clone genérico | Arduino UNO original | ESP32 (ou ESP32-S3) — mais RAM, Wi-Fi/Bluetooth integrados e possibilidades de expansão |
| Fonte de alimentação | 5 V / 1 A — suficiente para servos leves | 5 V / 2 A — utilizada no protótipo, alimenta servo padrão com folga | 5–7 V / ≥ 3 A com regulador DC-DC — para servos de torque elevado e múltiplos periféricos |
| Estrutura | MDF fino ou isopor — menor custo, mas pode ser menos rígido e durável | Acrílico como base para a bola rolar e como suporte de todos os componentes do servo/motor (impressão 3D PLA para peças auxiliares) | Estruturas 3D impressas em PLA/ABS com reforços ou acrílico/alumínio usinado — maior rigidez e acabamento profissional |
Observações: nas versões básicas, servos analógicos de baixo custo podem não ter torque suficiente e o acrílico pode trincar; considere usar isopor para reduzir o peso. O protótipo utilizou Futaba S3003, acrílico como base e PLA impresso em 3D para suportes. A versão recomendada sugere o ESP32 por oferecer mais memória e conectividade (Wi-Fi/Bluetooth), além de servos de maior torque e sensores Sharp de maior alcance.
Os manuais técnicos (datasheets) dos componentes estão disponíveis na pasta zenit_science/datasheets na raiz do repositório. Consulte esses documentos para detalhes de pinagem, curvas de resposta, limites de operação e características elétricas. Para referência rápida:
- Datasheet do servo Futaba S3003 (Luxorparts) — especifica torque (3,2 kg·cm a 4,8 V; 4,1 kg·cm a 6 V), velocidade (0,19 s/60° a 6 V), corrente máxima (< 1 A) e outras características.
- Datasheet do Sharp GP2Y0A21 — destaca a faixa de medição de 10 a 80 cm e a resposta típica de 39 ms, bem como a corrente de operação típica de 30 mA.
- Datasheet oficial do Sharp GP2Y0A21YK — abrangente (veja diretório de datasheets para download, pois o site pode estar instável).
- Outros datasheets (MG996R, ESP32, etc.) também estão agrupados nessa pasta.
As ligações básicas permanecem as mesmas; caso opte por incluir LED e buzzer, conecte-os aos pinos indicados no firmware oficial (ledPin = 6, buzzerPin = 3) com resistores em série (~220 Ω para o LED e ~100 Ω para o buzzer). Não se esqueça de usar capacitores próximos ao servo e ao sensor para filtrar ruídos.
- Vcc → 5 V
- GND → GND
- OUT → A0 (igual ao
Analog_inno código)
- Vermelho → 5 V da fonte externa
- Marrom/Preto → GND (comum ao GND do Arduino)
- Laranja/Branco (sinal) → D9
- Base fixa (MDF/acrílico).
- Suporte do servo centralizado, horn alinhado ao centro da barra.
- Pivô/fulcro posicionado no centro da barra (reduz esforço do servo).
- Bola rola sobre a barra; o sensor Sharp deve ficar lateralmente apontando para a esfera (ou para a região central onde ela passa).
- Curso mecânico pequeno (±10–15°) evita saturação e melhora a estabilidade.
Este projeto vai além do sketch básico. Dentro do diretório assets do repositório GitHub você encontra todos os arquivos necessários para experimentar, calibrar e entender o sistema:
- Firmware oficial: em
assets/code/index/pid_balanceestá a versão que utilizamos na exposição. Ela inclui melhorias como filtro exponencial, zona morta (“deadband”), beep/LED quando a bola está estabilizada e histerese no servo. Leia o código e adapte os parâmetros conforme a sua mecânica. - Jogo PID: em
assets/code/game/indexhá um pequeno jogo/programa interativo em que o usuário pode “brincar” de equilibrar a bola ajustando os ganhos PID. Ele registra placar e sons para tornar o experimento mais lúdico; embora seja um protótipo e não funcione tão bem quanto o firmware oficial, é útil para aprender como as constantes influenciam o comportamento. - Calibrações: nas pastas
assets/calibra_buzzer,assets/calibra_sensoreassets/calibra_servohá sketches simples para testar o buzzer/LED, estimar a curva de distância do sensor e determinar o ângulo central do servo. Esses arquivos não são 100 % precisos, mas servem como base de calibração — ajuste os valores conforme o seu hardware e anote os resultados para usar no firmware principal. - Arquivos de impressão 3D e mídia: em
assets/files/3dFilesestão os STL das peças do protótipo. O subdiretórioassets/filestambém hospedará futuramente um banner e slides de apresentação para auxiliar na explicação do projeto.
Para utilizar os sketches de calibração:
- Abra o arquivo adequado (
buzzer_centro,sensor_curveouservo_zero, por exemplo) na IDE Arduino. - Carregue no Arduino e siga as instruções do Serial Monitor para identificar o ponto em que o buzzer/LED deve acender, a curva real do sensor de distância e o ângulo central do servo.
- Anote os valores medidos e atualize os parâmetros no firmware oficial.
A faixa doce para este sensor no contexto do projeto é entre 15 e 40 cm. Faça uma curva experimental: marque 15, 20, 25, 30 e 35 cm; leia analogRead() e confira a curva dist = 17569.7 × adc−1.2062. Se a leitura saturar muito perto ou muito longe, realoque o sensor ou ajuste o distance_setpoint. Luz ambiente e superfícies brilhantes podem atrapalhar — use anteparo (tubinho preto) no sensor e uma bola fosca.
- Comece com
Ki = 0,Kd = 0; suba oKpaté começar a oscilar (margem). - Adicione
Kdpara amortecer, reduzindo o overshoot. - Introduza
Kiaos poucos para remover o erro em regime; não exagere para não “embalar”. - Use o Serial Plotter da IDE para enxergar o erro e a resposta.
- Dê pequenos toques na barra/bola e observe o tempo de acomodação.
Algumas grandezas úteis para demonstrar o desempenho:
- Overshoot (%) — pico inicial acima do setpoint.
- Tempo de acomodação (s) — tempo até ficar estável (±5 %).
- Erro estacionário — desvio final médio.
- Taxa do loop (Hz) — com
period = 50 msa taxa é ≈ 20 Hz, mas pode ser reduzida para 20–30 ms se a mecânica permitir. - Filtros — o firmware oficial emprega filtro exponencial na leitura, histerese no servo e zona morta para evitar jitter.
Atenção ao comportamento do sensor: o Sharp GP2Y0A21 é modulado internamente. Como explica o artigo da Robot Research Lab, a modulação produz pulsos na saída que podem gerar ruído inesperado; se você ler o valor enquanto o sensor está atualizando, pode capturar picos de tensão até 1 V acima do valor real, dificultando leituras consistentes. Além disso, o sensor passa 32 ms transmitindo e apenas ~9 ms descansando, e não há maneira de saber exatamente quando o período de repouso ocorre. Quando a bola fica fora da faixa de detecção (longe do sensor), esses picos fazem a saída “ficar louca” — o famoso glitch de modulação. Adicionamos filtros e limitação de distância no firmware oficial para mitigar esse efeito, mas tenha ciência de que o bug é inerente à física do sensor.
- Expandir para 2D (plataforma com 2 servos) usando 4 sensores Sharp ou touch resistivo de 4 fios.
- Implementar telemetria (plotagem x tempo) via Serial/Processing/Excel ou web.
- Modo competição: medir o tempo em que a bola permanece no centro ou em checkpoints definidos.
- Abra a IDE Arduino e selecione Arduino UNO como placa.
- Se quiser testar o código base, cole o sketch apresentado no item 1. Para melhor desempenho, abra o arquivo
pid_balanceemassets/code/indexe carregue-o no Arduino. - Ligue o hardware: servo alimentado por fonte 5 V externa, LED e buzzer com resistores, GND comum.
- Faça a calibração do sensor e ajuste
distance_setpoint; se necessário, use os sketches de calibração para estimar os valores corretos. - Tune os ganhos
Kp,KieKdaté estabilizar. - Caso deseje, experimente o “jogo” PID em
assets/code/game/index.
- Nunca alimente servos pesados pela porta USB.
- Use capacitores próximos ao servo e ao sensor para atenuar ruído e picos.
- Instale uma chave liga/desliga e mantenha os cabos bem fixos.
Este projeto é de uso educacional. O sketch original foi criado por ELECTRONOOBS (links no cabeçalho do código). A equipe ZENIT (IFFar) adaptou, aprimorou o firmware e desenvolveu toda a documentação, peças 3D e material de apoio.
Quem quiser replicar ou derivar este projeto está livre para fazê-lo, desde que:
- Mantenha os créditos a ELECTRONOOBS pelo código base e à equipe ZENIT (IFFar) pelas adaptações.
- Cite o repositório oficial (github.com/emanueca/zenit_science) em qualquer material de divulgação.
- Não remova as notas de fonte e créditos presentes no código.
Este projeto está licenciado sob a GNU General Public License v3.0.
- Você pode usar, estudar, modificar e redistribuir.
- Trabalhos derivados devem manter a mesma licença (copyleft).
- Veja o arquivo
LICENSEpara o texto integral (inglês + tradução PT-BR de referência).