![]() |
Construire un lanceur de balles pour toutous. |
mars 2026 |
J'ai essayé plusieurs systèmes
- détecteur infrarouge
Cette solution a été rapidement abandonnée car le soleil déclenchait le mécanisme même sans balle.
Je suis donc revenue à utiliser un simple contact.

![]() |
![]() |
![]() |
![]() |



![]() |
![]() |
![]() |
Le lanceur est soit autonome soit il est possible d'effectuer des réglages et faire des tests depuis un point d'accès WiFi créé par le lanceur dès qu'il est alimenté.

// Lanceur de balle
// castoo.fr
// code libre mais un lien vers https://castoo.fr doit être lisiblement mentionné.
//
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include <Servo.h>
// === Wi-Fi Point d’accès ===
const char* ssid_ap = "Lanceur_Balle";
const char* password_ap = "chien1234";
// === Serveur Web ===
ESP8266WebServer server(80);
// === Broches ===
const int motorLeftPWM = D5;
const int motorRightPWM = D7;
const int motorEnable = D8;
const int ballSensorPin = D2; // Actif LOW (balle présente)
const int servoPin = D6;
const int ledStatus = D4; // LED d’état (active LOW)
// === Servo ===
Servo servoBall;
const int servoFerme = 0;
const int servoOuvert = 180;
// === Variables ===
int speedLeft = 0;
int speedRight = 0;
bool tirEnCours = false;
bool balleDetectee = false;
bool tirVerrouille = false;
unsigned long tirEndTime = 0;
bool capteur_optique = false; // a true si un capteur optique est utilisé sinon a false si capteur manuel avec rebond
// === Anti-rebond ===
unsigned long dernierChangementCapteur = 0;
const unsigned long delaiAntiRebond = 100; // ms
bool etatStableCapteur = HIGH; // par défaut pas de balle
// === Durées (ms) ===
const unsigned long delaiVerrou = 8000;
const unsigned long clignoInterval = 500;
// === EEPROM ===
const int addrLeft = 0;
const int addrRight = 4;
// === LED clignotement ===
unsigned long lastBlink = 0;
bool ledState = false;
// === Gestion LED ===
void gererLED() {
unsigned long now = millis();
if (tirEnCours) {
digitalWrite(ledStatus, LOW); // allumée fixe
}
else if (tirVerrouille) {
if (now - lastBlink >= 200) { // rapide = verrou
lastBlink = now;
ledState = !ledState;
digitalWrite(ledStatus, ledState ? LOW : HIGH);
}
}
else {
if (now - lastBlink >= clignoInterval) { // lent = prêt
lastBlink = now;
ledState = !ledState;
digitalWrite(ledStatus, ledState ? LOW : HIGH);
}
}
}
// === Rampe de démarrage progressive ===
void demarrageProgressif() {
Serial.println("Démarrage progressif moteurs...");
int maxSpeed = max(speedLeft, speedRight);
for (int p = 0; p <= maxSpeed; p += 100) {
analogWrite(motorLeftPWM, constrain(p, 0, speedLeft));
delay(200);
analogWrite(motorRightPWM, constrain(p, 0, speedRight));
delay(200);
}
}
// === Lancer tir ===
void lancerPreparationTir() {
if (speedLeft == 0 && speedRight == 0) {
Serial.println("Vitesse nulle — tir annulé.");
return;
}
Serial.println("Balle détectée → Démarrage moteurs...");
tirEnCours = true;
tirVerrouille = true;
servoBall.write(servoFerme);
delay(200);
demarrageProgressif(); // montée douce en vitesse
}
// === Gestion tir ===
void gererTir() {
unsigned long now = millis();
if (tirEnCours) {
servoBall.write(servoOuvert);
delay(3000);
analogWrite(motorLeftPWM, 0);
delay(500);
analogWrite(motorRightPWM, 0);
delay(500);
servoBall.write(servoFerme);
delay(1000);
tirEnCours = false;
tirEndTime = now;
Serial.println("Tir terminé — moteurs arrêtés, servo refermé.");
}
if (tirVerrouille && (now - tirEndTime >= delaiVerrou) && !tirEnCours) {
tirVerrouille = false;
Serial.println("Sécurité levée — prêt à tirer.");
}
}
// === Tir manuel ===
void handleTir() {
if (tirEnCours) {
server.send(200, "text/plain", "Tir déjà en cours !");
} else if (tirVerrouille) {
server.send(200, "text/plain", "Sécurité active — attendre 10 secondes.");
} else {
lancerPreparationTir();
server.send(200, "text/plain", "Tir manuel lancé !");
}
}
// === Test Servo ===
void handleTestServo() {
Serial.println("Test servo...");
servoBall.write(servoOuvert);
delay(2000);
servoBall.write(servoFerme);
delay(100);
server.send(200, "text/plain", "Test Servo effectué !");
}
// === Test Moteurs ===
void handleTestMoteurs() {
Serial.println("Test moteurs 2s...");
analogWrite(motorLeftPWM, speedLeft / 2);
delay(1000);
analogWrite(motorRightPWM, speedRight / 2);
delay(1000);
delay(4000); // On fait tourner pendant 4 secondes.
analogWrite(motorLeftPWM, 0);
delay(500);
analogWrite(motorRightPWM, 0);
delay(500);
server.send(200, "text/plain", "Test Moteurs effectué !");
}
// === Page Web ===
void handleRoot() {
String html = R"rawliteral(
<!DOCTYPE html><html><head>
<title>Lanceur de balle</title>
<style>
body { font-family: Arial; text-align:center; background:#f7f7f7; }
.slider { width: 300px; }
button { padding:10px 20px; margin:10px; font-size:16px; }
</style></head><body>
<h2>Controle Lanceur de Balle</h2>
<p>Wi-Fi : <b>Lanceur_Balle</b> (mdp: chien1234)</p>
<p>Balle : <span id="balle">?</span></p>
<p>Vitesse gauche : <span id="Lval"></span></p>
<input type="range" min="0" max="1023" value="%L%" class="slider" id="L">
<p>Vitesse droite : <span id="Rval"></span></p>
<input type="range" min="0" max="1023" value="%R%" class="slider" id="R">
<br>
<button onclick="tir()">TIR Manuel</button>
<button onclick="testServo()">Test Servo</button>
<button onclick="testMoteurs()">Test Moteurs</button>
<script>
let L=document.getElementById("L");
let R=document.getElementById("R");
let Lval=document.getElementById("Lval");
let Rval=document.getElementById("Rval");
Lval.innerHTML=L.value; Rval.innerHTML=R.value;
L.oninput=()=>{Lval.innerHTML=L.value;send()};
R.oninput=()=>{Rval.innerHTML=R.value;send()};
function send(){fetch(`/set?L=${L.value}&R=${R.value}`);}
function tir(){fetch(`/tir`).then(r=>r.text()).then(alert);}
function testServo(){fetch(`/testServo`).then(r=>r.text()).then(alert);}
function testMoteurs(){fetch(`/testMoteurs`).then(r=>r.text()).then(alert);}
setInterval(()=>{
fetch(`/ball`).then(r=>r.text()).then(t=>{
document.getElementById("balle").innerHTML=t;
});
},500);
</script></body></html>
)rawliteral";
html.replace("%L%", String(speedLeft));
html.replace("%R%", String(speedRight));
server.send(200, "text/html", html);
}
// === Réglage vitesses ===
void handleSetSpeed() {
if (server.hasArg("L")) speedLeft = server.arg("L").toInt();
if (server.hasArg("R")) speedRight = server.arg("R").toInt();
EEPROM.put(addrLeft, speedLeft);
EEPROM.put(addrRight, speedRight);
EEPROM.commit();
Serial.printf("Sauvegarde vitesses L=%d R=%d\n", speedLeft, speedRight);
server.send(200, "text/plain", "OK");
}
// === Capteur balle ===
void handleBall() {
bool balle = (etatStableCapteur == LOW);
server.send(200, "text/plain", balle ? "Présente " : "Absente ");
}
// === Détection balle avec anti-rebond ===
void gererDetectionBalle() {
bool lecture = digitalRead(ballSensorPin); // LOW = balle présente
if(capteur_optique){
if (lecture == LOW && !balleDetectee && !tirEnCours && !tirVerrouille) {
balleDetectee = true;
lancerPreparationTir();
} else if (etatStableCapteur == HIGH) {
balleDetectee = false;
}
}else{
// capteur manuel (on evite les rebonds)
unsigned long now = millis();
if (lecture != etatStableCapteur && now - dernierChangementCapteur > delaiAntiRebond) {
dernierChangementCapteur = now;
etatStableCapteur = lecture;
if (etatStableCapteur == LOW && !balleDetectee && !tirEnCours && !tirVerrouille) {
balleDetectee = true;
lancerPreparationTir();
} else if (etatStableCapteur == HIGH) {
balleDetectee = false;
}
}
}
}
void setup() {
Serial.begin(115200);
pinMode(motorLeftPWM, OUTPUT);
pinMode(motorRightPWM, OUTPUT);
pinMode(motorEnable, OUTPUT);
pinMode(ballSensorPin, INPUT);
pinMode(ledStatus, OUTPUT);
digitalWrite(motorEnable, HIGH);
digitalWrite(ledStatus, HIGH);
servoBall.write(servoFerme);
servoBall.attach(servoPin, 500, 2400);
servoBall.write(servoFerme);
EEPROM.begin(512);
EEPROM.get(addrLeft, speedLeft);
EEPROM.get(addrRight, speedRight);
if (speedLeft < 0 || speedLeft > 1023) speedLeft = 300;
if (speedRight < 0 || speedRight > 1023) speedRight = 300;
WiFi.mode(WIFI_AP);
WiFi.softAP(ssid_ap, password_ap);
Serial.println("\nPoint d'accès : Lanceur_Balle");
Serial.print("IP locale : ");
Serial.println(WiFi.softAPIP());
server.on("/", handleRoot);
server.on("/set", handleSetSpeed);
server.on("/ball", handleBall);
server.on("/tir", handleTir);
server.on("/testServo", handleTestServo);
server.on("/testMoteurs", handleTestMoteurs);
server.begin();
Serial.println("Serveur web prêt !");
}
void loop() {
server.handleClient();
gererDetectionBalle();
gererTir();
gererLED();
}