Plateforme Robotique
- Ivan Mauricio Rodríguez 1
- Andrés Mauricio Rodríguez 1
- Gerson Darío Piraquive 1
Contents |
[edit] Notes
| Pourcentage | Présentation 20% | Rapport 30% | Fonctionnement 50% |
|---|---|---|---|
| 1 Remise 10% | 1 | 1 | 1 |
| 2 Remise 30% | 2 | 2 | 2 |
| 3 Remise 30% | 2 | 2 | 2
|
| 4 Remise 30% | 5 | 5 | 5 |
[edit] Projet
Pour le projet final du cours de Systèmes Embarqués, on va travailler avec la carte SIE (appellée SAKC précédement). Cette carte compte principalement avec un SoC d'Ingenic Semiconductor Jz4725 et une FPGA Xilinx Spartan 3E XC3S500E, avec plusieurs péripheriques qui la faisent une solution multimédia optimale pour produits de MP3, MP4 et dispositifs mobiles portables. Le processeur du SoC est un XBurst core de 32 bits, qui utilise un ensemble d'instructions SIMD et RISC, en combinant une unité de post-traitement de vidéo.
[edit] Description
Le projet à réaliser est un bras mécanique qui va compter avec servomoteurs afin de controler l'angle de position de chaque articulation. Ce bras aura trois dégrés de liberté qui peuvent s'interpreter comme épaule, coude et pince, en utilisant pour différents fins comme saisir des objets, les lancer, les manipuler, etc. Comme application espécifique le bras prendra des objets d'une bande transporteuse et les placera dans un autre lieu. Pour placer l'objet, on va mettre un senseur de proximité dans un point determiné et quand celui-ci le detectera, la banda stoppera.
Pour son fonctionnement, les moteurs et servomoteurs ont besoin d'une etap de puissance, car ils ont besoin de beaucoup de courant. On utilisera un converteur DC-DC et des optocoupleurs pour mettre attention aux circuits utilisés. Pour le mouvement de la bande transporteuse on va utiliser des moteurs de courant continu controlés par un pont-H.
Afin d'avoir un control plus pratique des servomoteurs, on mettra trois potentiomètres pour augmenter ou réduire la tension qui arrive aux entrées analogiques aux terminals de l'ADC. Cette tension-là va correspondre avec un cycle utile de PWM.
[edit] Fonctions
La plateforme robotique aura les fonctions suivants:
- Prendre un objet.
- Lance l'objet.
- Controler le mouvement de chaque articulation du bras.
- Prendre des objets d'une bande transporteuse et les deplacer.
[edit] Outils Hardware
- Carte SIE
- Power Driver
Ce driver nous permettra controler les moteurs de deplacement de la plateforme dans les deux directiones et en plus il va nous donner une arret rapide, qui freiner immediatement le moteur.
- Convertiseurs DC-DC
Les convertiseurs vont nous servir pour alimenter les moteurs et les servomoteurs, en faisant une fonction de step-up de tension de 4.5 à 6V. Dans ce cas le courant de sortie est environ 500 mA. Si on considere que chaque moteur/servomoteur consomme une valeur maximale de 200 mA, c'est à dire, en l'exigeant completement, ces convertiseurs nous servent pour notre but.
- Servomoteurs (HiTec 311, Futaba S3006, TowerPro SMG995R)
Le range de cycles utiles des servos c'est de 193 à 238 pour la base, 222 à 234 pour le coude et 216 à 225 pour la pince.
- Optocoupleur (MOCD213)
[edit] Outils Software
- Lua
Lua est un langage puissant "embarqué". Cela signifie que Lua n'est pas un langage autonome, sinon une librairie qui peut être enlacée avec d'autres applications pour incorporer des facilités de Lua dans ces applications.
Lua possède aussi un interprte autonome qui execute des instructions (chunks) en temps réel, en agissant avec l'usager. De cette manière on va controler les mouvements du bras mecanique.
- Xilinx ISE WebPack
Outil pour créer les modules hardware dans la FPGA de la SIE.
- KiCAD
Avec cet outil on a conçu le circuit imprimé de montage superficiel.
[edit] Hardware
Pour la plateforme robotique le composant hardware va consiter basiquement en:
1. PWM (pulse width modulation). Tecnique pour laquelle on modifie le cycle de travail d'un signal periodique afin de controler la quantité d'énergie qui s'envoie à une charge.
2. Convertiseur ADC (Analog to digital converter). Conversion d'un signal d'entrée analogique en valeur binaire, pour pouvoir la manipuler dans les circuits numériques. Les signaux à convertir sont celles remises pour chacun des senseurs de proximité (TCRT1000) .
3. Controleur du composant HIP4020 (Full Bridge Power Driver). Driver de puissance specialement conçu pour moteurs DC petits.
[edit] Communication Processeur-Peripheriques
[edit] Peripheriques
On utilisera 5 peripheriques determinés pour le composant hardware décrit précédemment, c'est à dire, 3 modules PWM, 1 controleur d'ADC et 1 controleur du driver des moteurs.
[edit] PWM comme peripherique dans la FPGA
Ce peripherique va se charger de controler les servomoteurs disposés pour le bras.
Comme l'on remarque, celui-ci reçois 8 bits d'information du processeur (sram_data) qui determinent le cycle utile du signal periodique qui sort (pwm), la periode de ce signal est determinée pour un parametre interne du module appelé temp1.
Le diagramme de flux se montre ci-dessous où on explique le comportement du peripherique.
Le module montré ci-dessous est la seconde version du controleur des trois peripheriques utilisés pour le control des servomoteurs.
`timescale 1ns / 1ps
module pwm(pwm1, pwm2, pwm3,
wdBus, we, cs,
reset, clk);
parameter B = (7);
input clk, we, reset;
input [3:0] cs;
input [B:0] wdBus;
output pwm1, pwm2, pwm3;
//PWMparameter temp = 500000;
reg [25:0] temp1, temp2, temp3;
reg [25:0] counter1, counter2, counter3;
reg ck1, ck2, ck3;
//initializeinitialbegincounter1 = 26'b0;
counter2 = 26'b0;
counter3 = 26'b0;
temp1 = 26'b0;
temp2 = 26'b0;
temp3 = 26'b0;
ck1=0;
ck2=0;
ck3=0;
end//--------------------------------------------------------------------------//PWM peripherial1always @(negedge clk)
beginif (we & cs[1] & wdBus != temp1) temp1 = wdBus * temp / 256;
endalways @(posedge clk)
begincounter1 = counter1 + 1;
if (counter1 <= temp1)
ck1 = 1'b1;
elsebeginif(counter1 == temp)
counter1 = 26'b0;
elseck1 = 1'b0;
endend//PWM peripherial2always @(negedge clk)
beginif (we & cs[2] & wdBus != temp2) temp2 = wdBus * temp / 256;
endalways @(posedge clk)
begincounter2 = counter2 + 1;
if (counter2 <= temp2)
ck2 = 1'b1;
elsebeginif(counter2 == temp)
counter2 = 26'b0;
elseck2 = 1'b0;
endend//PWM peripherial3always @(negedge clk)
beginif (we & cs[3] & wdBus != temp3) temp3 = wdBus * temp / 256;
endalways @(posedge clk)
begincounter3 = counter3 + 1;
if (counter3 <= temp3)
ck3 = 1'b1;
elsebeginif(counter3 == temp)
counter3 = 26'b0;
elseck3 = 1'b0;
endendassign pwm1 = ck1;
assign pwm2 = ck2;
assign pwm3 = ck3;
endmodule
La fréquence du signal PWM c'est de 100 HZ. On a chosit cette fréquence parce que quand on a fait les test avec les servos, ceux-ci ne demandaient pas beaucoup de courant (autour de 400 mA), et si on augmentais la fréquence jusqu'à 150 le courant augmentais quatre fois. Le cycle utile du signal est difinie par le vecteur de 8 bits sram_data.
[edit] H_BRIDGE
Pour le mouvement de la bande de la plateforme robotique on a disposé de deux moto-reducteurs, chacun est controlé par les drivers de puissance HIP4020.
Le composant reçois 3 signaux d'entrée (Brake, Direction y Enable) avec lesquelles il controle la direction du moteur connecté a celui-ci. Ces trois signaux sont aportées pour le processeur (sram_data).
La description d'hardware est la suivante:
`timescale 1ns / 1ps
module H_BRIDGE(brake, direction, enableM, wdBus, we, cs,reset, clk
);
parameter B = (7);
input clk, we, reset, cs;
input [B:0] wdBus;
output brake, direction, enableM;
reg [B:0] temp;
reg brakeB, directionB, enableB;
initialbegintemp = 8'b0;
brakeB = 0;
directionB = 0;
enableB = 0;
endalways @(negedge clk)
beginif (we & cs & wdBus != temp) begin
temp = wdBus;
endendalways @(posedge clk)
beginbrakeB = temp[0];
directionB = temp[1];
enableB = temp[2];
endassign brake = brakeB;
assign direction = directionB;
assign enableM = enableB;
endmodule
[edit] ADC
On a utilisé le controleur de l'ADC de l'example décrit en Scope. Ce controleur-là se base dans le suivant diagramme de temps.
[edit] Diagramme de Blocs
[File:BLOQUES.jpg|thumb|thumbtime=27|600px|center|Diagramme de blocs.]]
[edit] Schématique et PCB
[edit] Software
[edit] Utilisation de Lua
Comme mentionné dans les sections précédentes, Lua est utilisé pour exécuter l'action en temps réel et d'appeler les API écrites en C. Pour commencer, on a implémenté le PWM driver de périphérique. La référence a été pris comme l'exemple décrit dansLua_Blink_LED. Toutes les fonctions utilisé se trouvent reparties dans les fichiers jz47xx_gpio.c, Control.c, ADCw.c, sram_gpio_wrap.c. jz_adc_peripherial.c est le même fichier utilisé dans l'exemple de Scope/es qui prend la valeur du pointeur quand on fait le mapping des directions de la FPGA. Dans peripherials.lua on trouve les fonctions enlac'e avec les rutines.
Fonctions dans jz47xx_gpio.c
voidjz_belt (int stop)
{JZ_REG *virtual4;
virtual4 = ADCBuffer + 0x700;
if (stop==1)
{virtual4[0]=5;
usleep(500000);
}else{virtual4[0]=4;
usleep(500000);
}}voidjz_mapping (int duty, int motor)
{JZ_REG *virtual2, *virtual3, *virtual1;
switch(motor)
{case 1: virtual1 = ADCBuffer + 0x200;
virtual1[0] = duty;
break;
case 2: virtual2 = ADCBuffer + 0x400;
virtual2[0] = duty;
break;
default: virtual3 = ADCBuffer + 0x600;
virtual3[0]=duty;
break;
}}
- jz_belt() se charge d'écrire dans le péripherique du pont-H une valeur haute ou une valeur base pour que le moteur vire ou stoppe de virer.
- jz_mmaping() écrit la valeur du cycle utilse dans le servomoteur desiré.
Fonctions dans Control.c
#define ref 245#define inc 37#define inc2 55#define inc3 70int control1(float a)
{int b=215;
int a_ant = 0;
if(a <= a_ant+20) a=a_ant;
else if(a<=ref)b=193;
else if(a>ref && a<=(ref+inc)) b=195;
else if(a>(ref+inc) && a<=(ref+(inc*2))) b=197;
else if(a>(ref+inc*2) && a<=(ref+(inc*3))) b=199;
else if(a>(ref+inc*3) && a<=(ref+(inc*4))) b=201;
else if(a>(ref+inc*4) && a<=(ref+(inc*5))) b=203;
else if(a>(ref+inc*5) && a<=(ref+(inc*6))) b=205;
else if(a>(ref+inc*6) && a<=(ref+(inc*7))) b=207;
else if(a>(ref+inc*7) && a<=(ref+(inc*8))) b=209;
else if(a>(ref+inc*8) && a<=(ref+(inc*9))) b=211;
else if(a>(ref+inc*9) && a<=(ref+(inc*10))) b=213;
else if(a>(ref+inc*10) && a<=(ref+(inc*11))) b=215;
else if(a>(ref+inc*11) && a<=(ref+(inc*12))) b=217;
else if(a>(ref+inc*12) && a<=(ref+(inc*13))) b=219;
else if(a>(ref+inc*13) && a<=(ref+(inc*14))) b=221;
else if(a>(ref+inc*14) && a<=(ref+(inc*15))) b=223;
else if(a>(ref+inc*15) && a<=(ref+(inc*16))) b=225;
else if(a>(ref+inc*16) && a<=(ref+(inc*17))) b=227;
else if(a>(ref+inc*17) && a<=(ref+(inc*18))) b=229;
else if(a>(ref+inc*18) && a<=(ref+(inc*19))) b=231;
else if(a>(ref+inc*19) && a<=(ref+(inc*20))) b=233;
else if(a>(ref+inc*20) && a<=(ref+(inc*21))) b=235;
else if(a>(ref+inc*21)) b=238;
a_ant=a;
return b;
}int control2(float a)
{int b=225;
int a_ant = 0;
if(a <= a_ant+inc2) a=a_ant;
else if(a<=ref)b=220;
else if(a>ref && a<=(ref+inc)) b=221;
else if(a>(ref+inc2) && a<=(ref+(inc2*2))) b=222;
else if(a>(ref+inc2*2) && a<=(ref+(inc2*3))) b=223;
else if(a>(ref+inc2*3) && a<=(ref+(inc2*4))) b=224;
else if(a>(ref+inc2*4) && a<=(ref+(inc2*5))) b=225;
else if(a>(ref+inc2*5) && a<=(ref+(inc2*6))) b=226;
else if(a>(ref+inc2*6) && a<=(ref+(inc2*7))) b=227;
else if(a>(ref+inc2*7) && a<=(ref+(inc2*8))) b=228;
else if(a>(ref+inc2*8) && a<=(ref+(inc2*9))) b=229;
else if(a>(ref+inc2*9) && a<=(ref+(inc2*10))) b=230;
else if(a>(ref+inc2*10) && a<=(ref+(inc2*11))) b=231;
else if(a>(ref+inc2*11) && a<=(ref+(inc2*12))) b=233;
else if(a>(ref+inc2*12) && a<=(ref+(inc2*13))) b=234;
else if(a>(ref+inc2*13)) b=235;
a_ant=a;
return b;
}int control3(float a)
{int b=220;
int a_ant = 0;
if(a <= a_ant+inc3) a=a_ant;
else if(a<=ref)b=215;
else if(a>ref && a<=(ref+inc)) b=216;
else if(a>(ref+inc) && a<=(ref+(inc*2))) b=217;
else if(a>(ref+inc*2) && a<=(ref+(inc*3))) b=218;
else if(a>(ref+inc*3) && a<=(ref+(inc*4))) b=219;
else if(a>(ref+inc*4) && a<=(ref+(inc*5))) b=220;
else if(a>(ref+inc*5) && a<=(ref+(inc*6))) b=221;
else if(a>(ref+inc*6) && a<=(ref+(inc*7))) b=222;
else if(a>(ref+inc*7) && a<=(ref+(inc*8))) b=223;
else if(a>(ref+inc*8) && a<=(ref+(inc*9))) b=224;
else if(a>(ref+inc*9)) b=225;
a_ant=a;
return b;
}
- control1(), control2() y control3() envoient un cycle en accord avec une tension DC qui est changée pour les potentiometres.
Fonctions dans ADCw.c
void init()
{BUFFER_OFFSET = 8;
ADC_SPI_CLKDIV=ADC_SPI_CLKDIV_MAX;
BUFFER_LEN=16;
MUX_CHANNELS =0;
ADCBuffer = jz_adc_init();
for (int i = 0; i < 512; i++)
{ADCBuffer[i] = 0x00000000;
}adcConfig(ADC_CMD_SET_SPI_CLKDIV);
adcConfig(ADC_CMD_SET_FAST_CONV);
}float ADC(int servo)
{int a,b,c,d=0;
switch (servo)
{case 1:
printf("\nTomando muestras potenciometro 1...\n");
adcConfig(ADC_CMD_SET_CHANNEL3);
adcConfig(ADC_CMD_READ_CHANNEL3);
break;
case 2:
printf("\nTomando muestras potenciometro 2...\n");
adcConfig(ADC_CMD_SET_CHANNEL1);
adcConfig(ADC_CMD_READ_CHANNEL1);
break;
case 3:
printf("\nTomando muestras potenciometro 3...\n");
adcConfig(ADC_CMD_SET_CHANNEL2);
adcConfig(ADC_CMD_READ_CHANNEL2);
break;
case 4:
printf("\nTomando muestras del sensor...\n");
adcConfig(ADC_CMD_SET_CHANNEL0);
adcConfig(ADC_CMD_READ_CHANNEL0);
break;
default:
printf("\nError...\n");
}for(int i=BUFFER_OFFSET; i< BUFFER_LEN/2+BUFFER_OFFSET; i++)
{a=ADCBuffer[i]<<16;
b=a>>16;
c=ADCBuffer[i]>>16;
d=d+b+c;
}d=d/(BUFFER_LEN/2+BUFFER_OFFSET);
fflush (stdout);
return d;
}void adcConfig(uchar CMD)
{ADCBuffer[0] = (((MUX_CHANNELS<<6) + CMD)<<24) + \
((BUFFER_LEN+BUFFER_OFFSET*2) << 8) + \
(ADC_SPI_CLKDIV);
while(adcCheckBufferFull()) usleep(10000);
}int adcCheckBufferFull()
{return ADCBuffer[0]&0x20000000;
}
- init() initialise l'ADC avec une vitesse d'horloge minimum (97.65 kHz) et le configure en conversion rapide
- ADC() prend des échantillons d'un determiné canal, qui peut correspondre à quelque potentiometre ou au senseur de proximité et il sort une valeur moyenne de ces echantillons.
- adcConfig() selectionne un canal d'echantillonage ou une configuration de l'ADC et adcCheckBuffer() revise la capacité du Buffer.
Le fichier sram_gpio_wrap.c est fondamental pour l'usage de Lua parce que il contient le registre des fonctions décrites précédement.
De la même façon, dans le registre de ces fonctions on a ajouté quelque lignes de codage pour agrandir sa fonctionalité.
static int init_wrap(lua_State *L){
init();
return 0;
}static int take_sample_wrap(lua_State *L){
double color=ADC(4);
lua_pushnumber(L, color);
return 1;
}static int jz_mmap_wrap(lua_State *L){
int duty = luaL_checkint(L, 1);
int motor= luaL_checkint(L, 2);
jz_mapping(duty,motor);
return 0;
}static int usleep_wrap(lua_State *L){
usleep(500000);
return 0;
}static int control_wrap(lua_State *L){
int duty1_ant=0;
int duty2_ant=0;
int duty3_ant=0;
for(;;){
int duty1 = control1(ADC(1));
printf("Servo 1 --> %d",duty1);
int duty2 = control2(ADC(2));
printf("Servo 2 --> %d",duty2);
int duty3 = control3(ADC(3));
printf("Servo 3 --> %d",duty3);
usleep(10000);
if(duty1 != duty1_ant && duty1 < duty1_ant+10)
jz_mapping(duty1,1);
if(duty2 != duty2_ant)
jz_mapping(duty2,2);
if(duty3 != duty3_ant)
jz_mapping(duty3,3);
duty1_ant = duty1;
duty2_ant = duty2;
duty3_ant = duty3;
}return 0;
}static int jz_belt_wrap(lua_State *L){
int stop = luaL_checkint(L,1);
jz_belt(stop);
return 0;
}static const struct luaL_reg functions[] = {
{"init",init_wrap},
{"usleep",usleep_wrap},
{"control",control_wrap},
{"sample",take_sample_wrap},
{"mmap",jz_mmap_wrap},
{"belt",jz_belt_wrap},
{ NULL, NULL}
};
int luaopen_gpio(lua_State *L) {
luaL_newmetatable(L, metaname);
luaL_register(L, "gpio", functions);
return 1;
}
- init_wrap() ejecuta init().
- take_sample_wrap() échantillonne le capteur de proximité et renvoie la moyenne de ces échantillons.
- jz_mmap_wrap() prend deux arguments de la pile lua et les exécute jz_mmap().
- usleep_wrap() execute la fonction usleep() de la librairie time.h.
- control_wrap() se charge d'utiliser control(1), control(2) y control3() dans un cycle infini pour controler les servomoteurs avec les potentiometres.
- belt_wrap() arrête la bande en fonction de la valeur de l'argument est écrit dans le stack.
- luaL_reg enregistre les fonctions avec un nom spécifique afin qu'ils puissent travailler.
- luaL_register () crée un identifiant "gpio" pour appeler des fonctions dans la structure.
peripherials.lua est montré ci-dessous,
package.cpath = "./?.so"
require "gpio"
function Initial ()
gpio.mmap(215,1) usleep()
gpio.mmap(225,2) usleep()
gpio.mmap(225,3) usleep()
belt(1)
endfunction init()
gpio.init()
endfunction PWM (duty,motor)
gpio.mmap(duty,motor)
endfunction usleep()
gpio.usleep()
endfunction rutine_white()
Initial()
PWM(234,2) usleep() PWM(218,3) usleep()
PWM(225,2) usleep() PWM(225,1) usleep() PWM(237,1) usleep()
PWM(234,2) usleep() PWM(223,3) usleep() PWM(225,2) usleep() PWM(225,1)
endfunction rutine_black()
Initial()
PWM(234,2) usleep() PWM(218,3) usleep()
PWM(225,2) usleep() PWM(210,1) usleep() PWM(195,1) usleep()
PWM(234,2) usleep() PWM(223,3) usleep() PWM(225,2) usleep() PWM(210,1)
endfunction belt(stop)
gpio.belt(stop)
endfunction rutine()
color=gpio.sample()
print("Taking samples from Lua",color)
if color < 7 then
belt(0)
elseif color > 70 then
belt(1)
rutine_white()
elsebelt(1)
rutine_black()
endendfunction cycle()
for i=1,1000000 do
rutine()
endendfunction ctrl()
gpio.control()
endinit()
Initial()
Initial()
- Initial() place les servos dans une position initiale.
- PWM() écrit le cycle de travail dans un servo donnée.
- cycle() fonctionne sur un temps presque indéfinie de la routine de prendre l'objet à partir de la bande transporteuse selon la couleur et le met dans une certaine position.
- ctrl() exécute le cycle sans fin de la rédaction de position du servo avec les boutons.
D'abord on fait init() après Initial().
Toutes ces fonctions enregistrées et décrites dans peripherials.lua sont appelés en mode interactif avec la fonction lua dofile("peripherials.lua").
[edit] Driver
Le driver est créé pour le contrôle de 3 servos à travers un cycle de service envoyée par le même. Il a le même concept général et les mêmes performances que ce qui est fait dans l'espace utilisateur. Cependant, nous devons garder à l'esprit que dans ce cas, il a été tenu une cartographie virtuelle de la mémoire physique, mais les données sont envoyées directement à l'adresse physique(FPGA_BASE = 0xb5000000).
Dans le premier cas où le driver est chargé CS2 doit être configuré pour un fonctionnement correct
#define CS2_PIN JZ_GPIO_PORTB(26)
jz_gpio_set_function(CS2_PIN, JZ_GPIO_FUNC1);
Puis on procède à l'initialisation de chacun des servomoteurs avec des positions définies.
outb(215,FPGA_BASE + 0x800);
outb(225,FPGA_BASE + 0x1000);
outb(220,FPGA_BASE + 0x1800);
Après avoir terminé ce module est chargé.
L'écriture dans la même entrée a le rapport cyclique suivi d'une virgule et le servomoteur pour envoyer les données (225,1). La fonction d'écriture du driver est indiquée ci-dessous:
static ssize_tdevice_write(struct file *filp, const char *buff, size_t count, loff_t * off)
{int cmd = buff[0]-48;
int cmda = buff[1]-48;
int cmdb = buff[2]-48;
int cmdc = buff[3]-48;
int cmdd = buff[4]-48;
int cmde = 0;
int dir;
if(cmdc == -4)
{cmde = cmd*100 + cmda*10 + cmdb;
printk(KERN_INFO "Dutyout %d Servo %d\n", cmde, cmdd);
dir = cmdd*0x800;
outb( cmde , FPGA_BASE + dir);
}return 1;
}
On soustrait le nombre 48, car cette fonction prend une chaîne et comme on envoi un certain numéro, cela est pris en code ASCII. 5 dossiers sont utilisés pour prendre chacun des «numéros» reçu à la fonction, si les données sont correctement utilisées seront l'quatrième album ',' (ASCII 44) entrant dans le conditionnel et l'envoi du rapport cyclique choisi pour le servo .
Un exemple d'utilisation de cette fonction est indiqué ci-dessous (le nœud de communication doit être créé mknod /dev/pwm c 252 0)
$ echo '215,1'>/dev/pwm
Ceci enverra un cycle de 215 devoir de l'une servomoteur. Vous pouvez également envoyer des données à chaque servo simultanément.
$ echo '215,1 230,2 216,3' > /dev/pwm
Dans ce cas, on envoie un rapport cyclique de 215 à 1 servo, 230 à 216 servo servo 2 et 3.