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 est composée principalement d'un SoC d'Ingenic Semiconductor Jz4725 et d'une FPGA Xilinx Spartan 3E XC3S500E, ainsi que des plusieurs périphériques qui la font 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 plus d'une unité de post-traitement de vidéo.
[edit] Description
Le projet à réaliser est un bras mécanique qui utilise des servomoteurs afin de contrôler l'angle de position de chaque articulation. Ce bras aura trois degrés de liberté qui peuvent s’interpréter comme épaule, coude et pince, pour différents fins comme saisir des objets, les lancer, les manipuler, etc. Comme application spé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 déterminé auparavant et quand celui-ci détectera l'objet, la bande s'arrêtera.
Pour son fonctionnement, les moteurs et servomoteurs ont besoin d'une étape de puissance, car ils ont une demande assez importante de courant. On utilisera donc un convertisseur DC-DC et des optocoupleurs afin de découpler les étapes électroniques. Pour le mouvement de la bande transporteuse des moteurs de courant continu contrôlés par un pont-H seront utilisés.
Afin d'avoir un contrôle plus pratique des servomoteurs, on mettra trois potentiomètres pour augmenter ou réduire la tension qui arrive aux entrées analogiques des terminaux de l'ADC. Cette tension correspondra avec un cycle utile de PWM.
[edit] Fonctions
La plateforme robotique aura les fonctions suivants:
- Prendre un objet.
- Lancer l'objet.
- Contrôler le mouvement de chaque articulation du bras.
- Prendre des objets d'une bande transporteuse et les déplacer.
[edit] Outils Hardware
- Carte SIE
- Power Driver
Ce driver nous permettra de contrôler les moteurs pour le déplacement de la plateforme dans les deux directions, en plus il va nous donner une «arrêt rapide», qui freinera immédiatement le moteur.
- Convertisseurs DC-DC
Les convertisseurs 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 considère que chaque moteur/servomoteur consomme une valeur maximale de 200 mA, c'est à dire, en l'exigeant complètement, les convertisseurs choisis nous servent pour notre but.
- Servomoteurs (HiTec 311, Futaba S3006, TowerPro SMG995R)
Le range de cycles utiles des «servos» 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 de script «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 interprète autonome qui exécute des instructions (chunks) en temps réel, en agissant avec l'usager. En utilisant cet caractéristique on va contrôler les mouvements du bras mécanique.
- 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 module hardware est composé principalement de:
1. PWM (pulse width modulation). Technique par laquelle on modifie le cycle de travail d'un signal périodique afin de contrôler la quantité d'énergie qui s'envoie à une charge.
2. Convertisseur ADC (Analog to digital converter). Conversion d'un signal d'entrée analogique en valeur binaire, afin de pouvoir la manipuler dans les circuits numériques. Les signaux à convertir sont celles remises pour chacun des senseurs de proximité (TCRT1000) .
3. Contrôleur du composant HIP4020 (Full Bridge Power Driver). Driver de puissance spécialement conçu pour moteurs DC de petite taille.
[edit] Communication Processeur-Périphériques
[edit] Périphériques
On utilisera 5 périphériques déterminés par le composant hardware décrit précédemment, c'est à dire, 3 modules PWM, 1 contrôleur d'ADC et 1 contrôleur du driver des moteurs.
[edit] PWM comme périphérique dans la FPGA
Ce périphérique va se charger de contrôler les servomoteurs disposés pour le bras.
Comme l'on remarque, celui-ci reçois 8 bits d'information du processeur (sram_data) qui déterminent le cycle utile du signal périodique qui sort (pwm), la période de ce signal est déterminée à travers d'un paramétré interne du module appelé temp1.
Le diagramme de flux se montre ci-dessous où on explique le comportement du périphérique.
Le module montré ci-dessous est la seconde version du contrôleur des trois périphériques utilisés pour le contrôle 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 choisit 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 définie 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 contrôlé par les drivers de puissance HIP4020.
Le composant reçois 3 signaux d'entrée (Brake, Direction y Enable) avec lesquelles il contrôle la direction du moteur connecté a celui-ci. Ces trois signaux sont apportées par 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 contrôleur de l'ADC de l’exemple décrit en Scope. Ce contrôleur se base dans le diagramme de temps ci-dessous.
[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.