PiBot : Robot pianiste Arduino pour performance musicale de précision
Composants et fournitures
| Composant | Quantité |
|---|---|
| Arduino 101 | 1 |
| Registre à décalage 74HC595 (Texas Instruments) | 11 |
| MOSFET N-Channel IRFZ44N | 88 |
| Batterie 12V (type voiture) | 2 |
| Condensateur 100 µF | 11 |
| Câble OpenBuilds (au mètre) | 1 (beaucoup) |
| Plaque d'essai (breadboard) ou circuit imprimé générique | 11 |
| Support IC 8 broches (optionnel) | 11 |
| Résistance 10 kΩ (recommandée par MOSFET) | 88 |
| Diode 1N4007 (recommandée par MOSFET) | 88 |
Outils nécessaires
- Pistolet à colle chaude
Applications et services en ligne
- Processing (IDE)
- Python
- Convertisseur MIDI binaire vers texte (flashmusicgames.com)
À propos du projet
Genèse
Il y a des années, j'ai découvert un piano automatique Yamaha derrière la vitrine d'un magasin de musique. Ce projet est né d'une simple envie : construire mon propre robot pianiste.
Vue d'ensemble
Contrôler 88 solénoïdes (une par touche) avec deux Arduino Mega coûterait ~80 $. Solution économique : un seul Arduino (Uno/101) + 11 registres à décalage 74HC595. Chaque 595 pilote 8 sorties → 11 × 8 = 88 canaux.
Au lieu d'envoyer du MIDI brut, le PC transmet des trames de bits précalculées. Le fichier MIDI est converti en texte, puis en 11 octets (88 bits) par pas de temps via un script Python.
Matériel

Les solénoïdes commandés en Chine étaient trop faibles pour enfoncer les touches depuis l'intérieur. Solution : les alimenter en 24V au lieu de 12V (survoltestage contrôlé).

88 solénoïdes consomment beaucoup de courant. Faute d'alim secteur puissante, utilisation de 2 batteries de voiture 12V en série (24V).

Chaque carte contrôleur intègre un 595 (sur support IC) et 8 MOSFET IRFZ44N. Le schéma reprend l'exemple 2 de la doc Arduino ShiftOut (LED remplacées par la grille du MOSFET). Pas de résistance de pull-down sur la gate : les MOSFET s'ouvrent à ~5V et ferment sous ~4V (validé empiriquement).

Les solénoïdes sont collés sur une plaque plastique (colle chaude → remplacée plus tard par ruban double-face résistant à 150°C). Un fil relie chaque solénoïde au + de la batterie.
Logiciel
- Obtenir un fichier MIDI.
- Le convertir en texte via mid2txt.php (ignore signature rythmique, tempo, programme).
- Exécuter le script Python (fourni) pour générer 11 séries de 8 bits + timestamp.
- Envoyer les trames à l'Arduino via Processing (liaison série).


Note : Le code Python/Processing envoie les octets de droite à gauche car l'Arduino décale vers la droite sur le piano physique.
Dépannage
Problème majeur : surchauffe des solénoïdes faisant fondre la colle chaude. Solution : remplacement par ruban double-face haute température (150°C).
Code source
Python – Conversion MIDI → trames binaires
output_file = open("translated.txt", "w")
input_file = open("megalocania.txt")
raw_input_data = input_file.read()
work_data = raw_input_data.splitlines()
result = []
def main():
for a in work_data:
temp_time = time_finder(a)
if result == []:
result.append(str(temp_time) + ",")
if on_off_finder(a):
result[-1] += set_bit(True, note_finder(a))
elif not on_off_finder(a):
result[-1] += set_bit(True, note_finder(a))
elif time_finder_comm(result[-1]) == temp_time:
result[-1] = str(temp_time) + "," + set_bit_prev(on_off_finder(a), note_finder(a), -1)
elif time_finder_comm(result[-1]) != temp_time:
result.append(str(temp_time) + "," + set_bit_prev(on_off_finder(a), note_finder(a), -1))
for b in result:
output_file.write(b)
output_file.write("\n")
output_file.close()
def set_bit(On, note):
if note >= 21 and note <= 28 and On: return str(2**(note-21)) + ",0,0,0,0,0,0,0,0,0,0"
elif note >= 29 and note <= 36 and On: return "0," + str(2**(note-29)) + ",0,0,0,0,0,0,0,0,0"
elif note >= 37 and note <= 44 and On: return "0,0," + str(2**(note-37)) + ",0,0,0,0,0,0,0"
elif note >= 45 and note <= 52 and On: return "0,0,0," + str(2**(note-45)) + ",0,0,0,0,0,0"
elif note >= 53 and note <= 60 and On: return "0,0,0,0," + str(2**(note-53)) + ",0,0,0,0,0"
elif note >= 61 and note <= 68 and On: return "0,0,0,0,0," + str(2**(note-61)) + ",0,0,0,0"
elif note >= 69 and note <= 76 and On: return "0,0,0,0,0,0," + str(2**(note-69)) + ",0,0,0"
elif note >= 77 and note <= 84 and On: return "0,0,0,0,0,0,0," + str(2**(note-77)) + ",0,0"
elif note >= 85 and note <= 92 and On: return "0,0,0,0,0,0,0,0," + str(2**(note-85)) + ",0"
elif note >= 93 and note <= 100 and On: return "0,0,0,0,0,0,0,0,0," + str(2**(note-93)) + ",0"
elif note >= 101 and note <= 108 and On: return "0,0,0,0,0,0,0,0,0,0," + str(2**(note-101))
else: return "0,0,0,0,0,0,0,0,0,0,0"
def set_bit_prev(On, note, index):
temp = result[index]
temp = temp[(temp.find(",")+1):]
if note >= 21 and note <= 28:
local_temp = temp[0:temp.find(",")]
if On: return str(int(local_temp) + (2**(note-21))) + temp[temp.find(","):]
else: return str(int(local_temp) - (2**(note-21))) + temp[temp.find(","):]
# ... (logique similaire pour les 10 autres registres)
# Fonctions utilitaires : indexTh, time_finder, time_finder_comm, note_finder, on_off_finder
main()
Processing – Envoi série vers Arduino
import processing.serial.*;
Serial myPort;
String[] inputLines;
void setup() {
myPort = new Serial(this, "COM3", 9600);
inputLines = loadStrings("translated.txt");
run();
}
void run() {
int lastTime = 0;
for (int i = 0; i < inputLines.length; i++) {
String temp = inputLines[i];
delay((Integer.parseInt(temp.substring(0, temp.indexOf(","))) - lastTime) * 5); // *5 = multiplicateur tempo
lastTime = Integer.parseInt(temp.substring(0, temp.indexOf(",")));
send(temp.substring(temp.indexOf(",") + 1));
}
}
void send(String data) {
// Inversion ordre : le fichier texte suppose right-to-left, mais l'Arduino décale left-to-right
String eleventh = data.substring(0, indexOforder(data, ",", 1));
String tenth = data.substring(indexOforder(data, ",", 1)+1, indexOforder(data, ",", 2));
// ... jusqu'à first
String first = data.substring(indexOforder(data, ",", 10)+1);
myPort.write("888f"); // Début trame
myPort.write(first + "f");
myPort.write(second + "f");
// ... jusqu'à eleventh
myPort.write(eleventh + "f");
myPort.write("999f"); // Fin trame
}
int indexOforder(String data, String find, int order) {
int currentOrder = 0, lastLocation = 0;
while (currentOrder < order) {
lastLocation = data.indexOf(find, lastLocation+1);
currentOrder++;
}
return lastLocation;
}
Arduino – Réception et pilotage des 595
// Code simple : lit le série, 888 = latch HIGH, 999 = latch LOW
// Décale les 11 octets reçus vers les 595 en cascade
// (Téléchargement uniquement - voir dépôt GitHub)
Schémas

Schéma équivalent à l'exemple ShiftOut d'Arduino, avec MOSFET à la place des LED. Voir aussi la doc officielle Arduino.
Processus de fabrication
- Construire un robot Raspberry Pi piloté par Bluetooth avec retour audio
- Construire un robot piloté par Internet avec streaming vidéo Arduino & Raspberry Pi
- Construire un robot Raspberry Pi piloté Wi-Fi avec Python – Guide pas à pas
- Contrôler des LED avec Alexa via Raspberry Pi – Guide Pas à Pas
- Construire un Iron Man animé avec Arduino : capteurs, sons et relais
- Find Me : Localisateur d'objets intelligent avec Arduino et Bluetooth
- Contrôleur d'humidificateur DIY avec Arduino et relais – Installation haute tension sécurisée
- Robot commandé par la voix avec Arduino Nano et Bluetooth
- MobBob : Construisez votre propre robot Arduino, contrôlé via smartphone Android