Aujourd’hui on va tenter de détourner l’électronique du robot-aspirateur et essayer de s’interfacer avec via un Raspberry-Pi et un Arduino home-made (un ATmega328P-PU alimenté par le 3.3v du Pi, sur son oscillateur 8MHz interne).
L’objectif, c’est transformer l’ex-aspirateur-debilos en robot radio-commandé par Wi-Fi (via SSH) et par un joypad de Xbox360, pour commencer gentiment et vérifier que tout fonctionne comme prévu. Une fois cette étape passée, il ne restera (presque) plus qu’à s’occuper des capteurs et de la logique interne du robot…
Après examen du PCB principal, le plus rapide, pour le moment, semble de détourner les 2 moteurs, récupérer le 9V des moteurs directement sur un de leurs connecteurs, et les piloter avec 2 transistors via l’ATmega, via le Pi.
On ne dispose pas de H-bridge dans nos composants de récup’ pour remplacer les transistors, d’où ce choix. Un seul composant H-bridge permettrait de pouvoir contrôler la vitesse de chaque moteur, et dans les 2 sens. Avec notre solution des transistors, on sera pour le moment bloqué avec uniquement la marche avant pour chaque moteur, mais c’est déjà pas si mal pour un début. A suivre pour une éventuelle solution alternative…
– on bloque le capteur optique avec un bout de carton ou autre pour avoir un 9V continu sur les 2 connecteurs moteurs,
– le 9V des moteurs est donc récupéré d’un des connecteurs moteurs,
– on connecte 2 transistors aux moteurs et à l’ATmega, selon le schéma suivant :
(les résistances font 330ohms mais ce n’est pas critique)
– on connecte les pins 1 (Rx) et 2 (Tx) de l’ATmega328 aux pins 8 (TxD) et 10 (RxD) du Raspberry. Puisque l’ATmega tourne sur le 3.3V du Pi, on a pas à s’inquiéter de différences entre niveaux logiques, tout est sur une base de 3.3V,
– la pile représente le +9V qui est repiqué sur l’un des connecteurs moteurs, sur le PCB d’origine,
– on connecte le Pi sur sa batterie dédiée pour le moment. Si possible, on l’alimentera plus tard directement sur la batterie du robot.
– on relie le tout à une masse commune
Du côté du Raspberry Pi, qui est connecté à notre réseau local via Wi-Fi, on va utiliser plusieurs composants, qui seront les bases du « cerveau » de notre robot :
– un premier script en python, le « server_udp.py », crée un serveur UDP, attend des commandes de type « 7/5/200/ » (explication en dessous) sur un port prédéfini et les renvoi directement à l’ATmega via les GPIO UART
– un second script python « client_udp.py » nous permettra -temporairement- de piloter le robot à distance grace à un joypad xbox360 dans un premier temps, histoire de vérifier que l’électronique et la liaison Raspi/ATmega fonctionnent bien.
Je vous fais grâce d’un dernier script python, pas super intéressant, qui fait exactement la même chose que client_udp.py mais via une invite en ligne de commandes (donc le robot est pilotable par SSH de cette manière).
Sur l’ATmega328, le code attend des commandes de type « 7/5/200/ » sur ses ports GPIO UART, les traduit en instructions, et renvoi le résultat le cas échéant. Par exemple :
– « 1/1/2/1/ » -> write / analog / pin 2 / HIGH
– « 1/1/4/0/ » -> write / analog / pin 4 / LOW
– « 2/2/3/ » -> read / digital / pin 3
– « 7/3/255/ » -> write PWM / pin 3 / value 255
– etc…
Ce code (beta, pas testé à 100%) est censé pouvoir gérer les lectures/ecritures analog/digital, le controle PWM, le controle servos (non testé), les shift register 595 et les sensors ultrasons pour le moment. Pour les curieux qui décortiqueront le code, une commande spéciale « 42 » est envoyée par le Pi et oblige l’ATmega à répondre « arduino », ce qui permet aux 2 entités, Pi et Atmega, de s’identifier en début de dialogue (les geeks comprendront la référence ^^).
C’est promis, je ferai un article complet et détaillé sur le sujet.
L’ATmega328P-PU est cablé puis programmé via un Arduino Uno chargé avec le code Arduino-ISP, puis installé sur son petit PCB dédié, connecté au Pi. Un article sera également rédigé d’ici peu au sujet de cette méthode de programmation d’un micro-controlleur.
Après la mise en place (temporaire) et le branchement de tout ce petit monde, ca donne ce résultat :

Il ne reste plus qu’à tester le tout…
Cooooool !!! Bon maintenant qu’on a un tank télé-commandé super cool avec plein de gadgets (j’abuse à peine ^^), il est temps de bosser sur son cerveau…
Voici les 3 scripts dont j’ai parlé un peu au dessus. Ils sont loin d’être mis au propre, puisque je poste au fur et à mesure des avancées, et on doit aller vite, très vite pour tout faire. En cas de problème, n’hésitez pas à demander en commentaires !
#include <Servo.h>
#include <RCSwitch.h>
unsigned long serialdata;
int inbyte;
int servoPose;
int servoPoses[80] = {};
int attachedServos[80] = {};
int servoPin;
int pinNumber;
int sensorVal;
int analogRate;
int digitalState;
Servo myservo[] = {};
int led = 13;
int blinkdelay = 1;
// Shift registers
int srd;
int srl;
int src;
int srdata;
// rf 433
RCSwitch mySwitch = RCSwitch();
int rf433Pin;
int rf433Data1;
int rf433Data2;
void setup()
{
Serial.begin(9600);
pinMode(led, OUTPUT);
digitalWrite(led, LOW);
}
void blinkLED(byte targetPin, int numBlinks, int blinkRate) {
for (int i=0; i < numBlinks; i++) {
digitalWrite(targetPin, HIGH); // sets the LED on
delay(blinkRate); // waits for blinkRate milliseconds
digitalWrite(targetPin, LOW); // sets the LED off
delay(blinkRate);
}
}
void tobin(int valeur, int digit, int nombre[])
{
int i;
int temp;
temp=valeur;
for (i=0;i<digit;i++){ // itère pour le nombre de digit attendus nombre[i]=temp & 1; // prend le LSB et le sauve dans nombre temp = temp >> 1; // décalage d'un bit sur la droite
}
}
void loop()
{
getSerial();
blinkLED(led, 1, blinkdelay);
switch(serialdata)
{
// PIN WRITE
case 1:
{
//blinkLED(led, 1, blinkdelay);
//analog digital write
getSerial();
switch (serialdata)
{
case 1:
{
//analog write
getSerial();
pinNumber = serialdata;
getSerial();
analogRate = serialdata;
pinMode(pinNumber, OUTPUT);
analogWrite(pinNumber, analogRate);
pinNumber = 0;
break;
}
case 2:
{
//digital write
getSerial();
pinNumber = serialdata;
getSerial();
digitalState = serialdata;
pinMode(pinNumber, OUTPUT);
if (digitalState == 0)
{
digitalWrite(pinNumber, LOW);
}
if (digitalState == 1)
{
digitalWrite(pinNumber, HIGH);
}
pinNumber = 0;
break;
}
}
break;
}
// PIN READ
case 2:
{
//blinkLED(led, 1, blinkdelay);
getSerial();
switch (serialdata)
{
case 1:
{
//digital read
getSerial();
pinNumber = serialdata;
pinMode(pinNumber, INPUT);
sensorVal = digitalRead(pinNumber);
Serial.println(sensorVal);
sensorVal = 0;
pinNumber = 0;
break;
}
case 2:
{
//analog read
getSerial();
pinNumber = serialdata;
pinMode(pinNumber, INPUT);
sensorVal = analogRead(pinNumber);
Serial.println(sensorVal);
sensorVal = 0;
pinNumber = 0;
break;
}
}
break;
}
// SERVO
case 3:
{
//blinkLED(led, 1, blinkdelay);
getSerial();
switch (serialdata)
{
case 1:
{
//servo read
getSerial();
servoPin = serialdata;
Serial.println(servoPoses[servoPin]);
break;
}
case 2:
{
//servo write
getSerial();
servoPin = serialdata;
getSerial();
servoPose = serialdata;
if (attachedServos[servoPin] == 1)
{
myservo[servoPin].write(servoPose);
}
if (attachedServos[servoPin] == 0)
{
Servo s1;
myservo[servoPin] = s1;
myservo[servoPin].attach(servoPin);
myservo[servoPin].write(servoPose);
attachedServos[servoPin] = 1;
}
servoPoses[servoPin] = servoPose;
break;
}
case 3:
{
//detach
getSerial();
servoPin = serialdata;
if (attachedServos[servoPin] == 1)
{
myservo[servoPin].detach();
attachedServos[servoPin] = 0;
}
}
}
break;
}
// Shift register
case 4:
{
//blinkLED(led, 1, blinkdelay);
getSerial();
srd = serialdata;
getSerial();
srl = serialdata;
getSerial();
src = serialdata;
getSerial();
srdata = serialdata;
pinMode(srd, OUTPUT);
pinMode(srl, OUTPUT);
pinMode(src, OUTPUT);
digitalWrite(srl, LOW);
shiftOut(srd, src, MSBFIRST, srdata);
digitalWrite(srl, HIGH);
// Serial.println(srdata);
break;
}
// prise RF433 send
case 5:
{
//blinkLED(led, 1, blinkdelay);
getSerial();
rf433Pin = serialdata;
Serial.println(rf433Pin);
getSerial();
rf433Data1 = serialdata;
Serial.println(rf433Data1);
getSerial();
rf433Data2 = serialdata;
Serial.println(rf433Data2);
getSerial();
mySwitch.enableTransmit(rf433Pin);
switch (serialdata)
{
case 0:
{
mySwitch.switchOff(rf433Data1, rf433Data2);
}
case 1:
{
mySwitch.switchOn(rf433Data1, rf433Data2);
}
}
break;
}
// HC-SR04
case 6:
{
getSerial();
int trigPin = serialdata;
getSerial();
int echoPin = serialdata;
long lecture_echo;
// init pins
pinMode(trigPin, OUTPUT);
digitalWrite(trigPin, LOW);
pinMode(echoPin, INPUT);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
lecture_echo = pulseIn(echoPin, HIGH);
Serial.println(lecture_echo);
break;
}
// DC Motor
case 7:
{
getSerial();
int pwmPin = serialdata;
getSerial();
int pwmVar = serialdata;
pinMode(pwmPin, OUTPUT);
analogWrite(pwmPin, pwmVar);
break;
}
// Identification
case 42:
{
//blinkLED(led, 3, 10);
Serial.println("arduino");
break;
}
}
}
long getSerial()
{
serialdata = 0;
while (inbyte != '/')
{
inbyte = Serial.read();
// inbyte = Serial.read()-'0';
if (inbyte > 0 && inbyte != '/')
{
serialdata = serialdata * 10 + inbyte - '0';
}
}
inbyte = 0;
return serialdata;
}
server_udp.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import os
import threading
from datetime import datetime, time
import math
from time import sleep
DEBUG = 1
# --- Le serveur UDP ---
# Importe socket..
from socket import *
# Variables
host = "localhost"
port = 2074
buf = 128
addr = (host,port)
# ----------------------
# --- arduino - serial ---
import serial
locations=['/dev/ttyAMA0', '/dev/ttyUSB0','/dev/ttyUSB1','/dev/ttyUSB2','/dev/ttyUSB3',
'/dev/ttyS0','/dev/ttyS1','/dev/ttyS2','/dev/ttyS3']
get_uart_delay = 5
device = ""
found_device = ""
arduino = ""
# -----------------------
write_dir = "/run/shm/variables"
class MyTimer:
def __init__(self, tempo, target, args= [], kwargs={}):
self._target = target
self._args = args
self._kwargs = kwargs
self._tempo = tempo
def _run(self):
self._timer = threading.Timer(self._tempo, self._run)
self._timer.start()
self._target(*self._args, **self._kwargs)
def start(self):
self._timer = threading.Timer(self._tempo, self._run)
self._timer.start()
def stop(self):
self._timer.cancel()
def get_uart_device():
global device
global found_device
global arduino
for device in locations:
if DEBUG == 1:
print "udp2uart : Trying...",device
try:
arduino = serial.Serial(device, 9600, timeout=2, bytesize=8, parity='N', stopbits=1)
try:
arduino.write("42/")
# pygame.time.wait(0.1)
answer = format(arduino.readline().strip('\r\n').strip())
var_type = type(answer)
#print(var_type)
if ("arduino" in answer):
arduino.close()
arduino.open()
found_device = device
if DEBUG == 1:
print("udp2uart : Arduino trouvé sur "+device+"")
os.system("echo "+found_device+" > "+write_dir+"/arduino")
break
else:
print("udp2uart : mauvaise réponse de "+device+" ("+answer+")")
found_device = ""
os.system("echo "+found_device+" > "+write_dir+"/arduino")
except:
found_device = ""
os.system("echo "+found_device+" > "+write_dir+"/arduino")
if DEBUG == 1:
print("udp2uart : echec de l'envoi des données à "+device+"")
except:
found_device = ""
os.system("echo "+found_device+" > "+write_dir+"/arduino")
if DEBUG == 1:
print("udp2uart : echec de la connexion à "+device+"")
def trunc(f, n):
'''Truncates/pads a float f to n decimal places without rounding'''
return ('%.*f' % (n + 1, f))[:-1]
def uart_dialog(cmd,source_addr):
command = cmd.split('/')
if DEBUG == 1:
# pin write
if command[0] == "1":
# digital
if command[1] == "1":
pin = command[2]
freq = command[3]
print("udp2uart : digital write pin "+pin+" "+freq+"")
# analog
elif command[1] == "2":
pin = command[2]
if command[3] == "0":
pinstate = "LOW"
elif command[3] == "1":
pinstate = "HIGH"
print("udp2uart : analog write pin "+pin+" "+pinstate+"")
# pin read
elif command[0] == "2":
# digital
if command[1] == "1":
pin = command[2]
print("udp2uart : digital read pin "+pin+"")
# analog
elif command[1] == "2":
pin = command[2]
print("udp2uart : analog read pin "+pin+"")
# servo
elif command[0] == "3":
# read
if command[1] == "1":
pin = command[2]
print("udp2uart : servo read pin "+pin+"")
# write
elif command[1] == "2":
pin = command[2]
pos = command[3]
print("udp2uart : servo write pin "+pin+" pos "+pos+"")
# shift register
elif command[0] == "4":
dataPin = command[1]
latchPin = command[2]
clockPin = command[3]
SRegdata = command[4]
print("udp2uart : shift register write "+SRegdata+" (data : "+dataPin+", latch : "+latchPin+", clock : "+clockPin+"")
# PING sensor
elif command[0] == "6":
trigPin = command[1]
echoPin = command[2]
print("udp2uart : PING sensor read (trigger : "+trigPin+", echo : "+echoPin+")")
# PWM
elif command[0] == "7":
pwmPin = command[1]
pwmVar = command[2]
print("udp2uart : PWM write (pin : "+pwmPin+", var : "+pwmVar+")")
if arduino != "":
try:
arduino.write(data)
if command[0] == "2":
# sleep(0.01)
read_data=arduino.readline().strip('\r\n').strip()
if read_data:
if read_data == "nan":
sensor_value = "data error"
else:
sensor_value = read_data
if(UDPSock.sendto(sensor_value,addr)):
print "udp2uart : Renvoi de "+str(sensor_value)+" vers "+str(addr)+""
else:
sensor_value = "read error"
elif command[0] == "6":
# sleep(0.01)
read_data=arduino.readline().strip('\r\n').strip()
if read_data:
if read_data == "nan":
sensor_value = "data error"
else:
sensor_value_cm = trunc((float(read_data) / 58), 2)
if(UDPSock.sendto(read_data,addr)):
print "udp2uart : Renvoi de "+read_data+" vers "+str(addr)+""
#else:
#if(UDPSock.sendto("OK",addr)):
#print "Renvoi de : OK vers "+str(addr)+" ... "
except:
print "udp2uart : erreur lors de l envoi de la commande"
os.system("mkdir "+write_dir+"")
while(found_device == ""):
get_uart_device()
if found_device == "":
sleep(5)
# get_uart_device()
# --- Ecoute socket UDP ---
# On fait le socket
# et bind l'addresse
UDPSock = socket(AF_INET,SOCK_DGRAM)
UDPSock.bind(addr)
while 1:
data,addr = UDPSock.recvfrom(buf)
if not data:
print "Le client a quitte"
break
else:
#print "Recu: "+data+" de "+str(addr)+""
uart_dialog(data,addr)
# On ferme tout ca
UDPSock.close()
# --- FIN Ecoute socket UDP ---
client_udp.py
# Le client
# On importe socket..
from socket import *
import uinput
import xbox_read
# Variables
host = "localhost"
port = 2074
buf = 1024
addr = (host,port)
i = 0
# On fait le socket
UDPSock = socket(AF_INET,SOCK_DGRAM)
def_msg = "Message?"
print "\n",def_msg
# events = pygame.event.get()
max_val = 32768
# Envois du message
while (1):
for event in xbox_read.event_stream(deadzone=12000):
if event.key == "Y1":
print("left")
motor = "5"
value = event.value/128
print(value)
data = "7/"+str(motor)+"/"+str(value)+"/"
print(data)
if value >= 0:
if(UDPSock.sendto(str(data),addr)):
print "("+str(i)+") Envoi de: '",data,"' ... "
if event.key == "Y2":
print("right")
motor = "3"
value = event.value/128
print(value)
data = "7/"+str(motor)+"/"+str(value)+"/"
if value >= 0:
if(UDPSock.sendto(str(data),addr)):
print "("+str(i)+") Envoi de: '",data,"' ... "

Une réflexion au sujet de « Makers – Robot Hack – Jour 2 »