jeudi 28 novembre 2013

Fabrication des pièces

Je vais présenter ici quelques pièces que j'ai réalisé.
Je remercie le responsable du laboratoire de mécanique où j'ai effectué mon dernier stage. Il a eu la bienveillance de m'apprendre à utiliser Inventor pour la conception 3D et aussi à utiliser les machines-outils afin d'être autonome. Sans cela je n'aurai peut-être pas entamé ce projet.


La tige de guidage

Pour cette pièce, un tube en acier d'environ 12 mm de diamètre et 400 mm de long, elle était trop longue à l'origine, il a donc fallut l'usiner pour l'adapter aux dimensions de l'ensemble.

Dessin technique

Tronçonnage et dressage au tour...


Ce sera plus parlant avec une vidéo.
Cette extrait nous montre les différentes étapes de la réalisation de cette pièce au tour.


mercredi 27 novembre 2013

Les premiers pas de l'asservissement en position

Après avoir implémenté le PID numérique sur la carte FPGA et réalisé l'interface avec Labview, voici ce que cela donne.


Dans la vidéo on peut voir l'algorithme en action. Les paramètres ne sont pas encore réglés mais on a déjà un avant gout de sont fonctionnement. Quand j'aurai la table terminé je m'occuperai des réglages et de l'implémentation de l'asservissement en vitesse.


On ne voit pas très bien sur la vidéo mais il est possible de changer les paramètres PID en temps réel.Quand j'augmente la valeur de l'intégral Ki le système diverge.


dimanche 29 septembre 2013

Transmission de la position sur LabView

Il serait intéressant de pouvoir d'afficher la position du chariot sur l'ordinateur pour régler l'asservissement. Pour cela j'utilise LabView avec lequel on peut acquérir, traiter des données et afficher des courbes. Dans cette partie je présenterai la méthode que j'ai utilisée pour transmettre les coordonnées en prenant en compte les spécificités du port série notamment la vitesse de transmission et nombre de bits par trame.

Méthode

Une première idée qui vient à l'esprit pour transmettre la position est simplement d'envoyer la valeur de cette position à chaque instant. Mais sur le chariot on peut avoir jusqu'à 7500 incréments avec 13 bits par incrément (2^13 = 8192). De plus, le temps pour passer de la position 0 à 7500 est grossièrement d'un quart de seconde à vitesse maximale, soit 30 000 positions par seconde. A 13 bits par position il faudrait d'un débit de 390 kbits/sec alors que le débit maximal du port série est de 115,2 kbits/sec.
Pour palier ce problème, au lieu d'envoyer la valeur brut, le système envoie la différence de position delta à intervalle de temps régulier, LabView se charge ensuite d'ajouter à la position initiale cette différence.
Pour un intervalle de 1 milliseconde, on a au maximum une différence de 30 incréments encodable sur 6 bits soit 5 bits pour la valeur et 1 bit de signe. Ainsi un débit de 6 kbits/sec est suffisant pour cette méthode.

Algorithme

Le logigramme suivant représente l'algorithme utilisé pour réaliser ceci.


Explication
A l'état initial, si enable le signal d'autorisation de comptage à la sortie du codeur passe à l'état haut, celui-ci est compté puis on passe à l'état START. Les incréments sont ajoutés pendant l'intervalle de temps. Par sécurité, pour ne pas oublier un incrément, si on en reçoit un à exactement 1 milliseconde, le temporisateur redémarre et on continu à incrémenter. Bien sur on dépasserai les 30 valeurs maximales, mais comme on peut encoder sur toute une trame de 8 bits, on a donc doit à 4 fois à la suite à grande vitesse, ce qui n'est pas très probable vu la vitesse de l'horloge.

Programme VHDL

entity FSM_delta is
port(
clk: in std_logic;
reset : in std_logic;
enable : in std_logic;
downup : in std_logic;
delta_done : out std_logic;
delta: out std_logic_vector(7 downto 0));
end FSM_delta;

architecture behavior of FSM_delta is
type state_type is (idle, start);
signal state_reg, state_next: state_type;
signal d_reg, d_next : std_logic_vector(7 downto 0) :=(others => '0');
signal cnt_reg, cnt_next : std_logic_vector(6 downto 0) :=(others => '0');
signal done_reg, done_next : std_logic := '0';

begin
process(clk, reset)
begin
if (reset = '1') then
state_reg <= idle;
d_reg <= (others => '0');
cnt_reg <= (others => '0');
d_reg <= (others => '0');
elsif (clk'event and clk='1') then
state_reg <= state_next;
d_reg <= d_next;
cnt_reg <= cnt_next;
done_reg <= done_next;
delta <= d_reg;
end if;
end process;
-- next state logic
process (clk, downup, d_reg, enable, cnt_reg, state_reg, done_reg)
begin
state_next <= state_reg;
d_next <= d_reg;
cnt_next <= cnt_reg;
done_next <= done_reg;
case state_reg is
when idle =>
done_next <= '0';
if (enable = '1') then
state_next <= start;
if (downup = '1')then
d_next <= d_reg + 1;
else d_next <= d_reg - 1;
end if;
end if;

when start =>
if (enable = '1') then
if (downup = '1')then d_next <= d_reg + 1; -- incrementation
else d_next <= d_reg - 1;                  -- decrementation
end if;
end if;
if (cnt_reg = "1100011" and enable = '1') then -- cnt_reg depend de l'horloge
cnt_next <= (others => '0');
state_next <= start;
elsif (cnt_reg = "1100011" and enable = '0') then
cnt_next <= (others => '0');
d_next <= (others => '0');
state_next <= idle;
done_next <='1';
else
cnt_next <= cnt_reg + 1;
end if;
end case;
end process;
delta_done <= done_reg;
end behavior;

Vidéo à suivre dans les prochains articles... 

mercredi 18 septembre 2013

PID numérique

Présentation

L’objectif de l’asservissement PID (Proportionnel Intégral Dérivé) est de stabiliser le système en fonction d’une consigne. On cherche à atteindre une position donnée dans le cas du chariot, en traitant l'erreur entre la consigne et la mesure fournit par le capteur par le contrôleur PID, la nouvelle valeur obtenue sert de commande pour le moteur. On aura donc compris que la commande joue simplement sur la vitesse du moteur à travers le PWM.

Structure d'une boucle d'asservissement:


Ce modèle est une structure en parallèle mais il existe plusieurs architectures de PID, en série ou mixte.

Il y a trois paramètres à régler:

  • L’action proportionnel P: amplifie l’erreur d’un facteur Kp (le gain) permettant une réaction plus rapide du système au changement de consigne en fonction du gain, mais peut faire diverger le système si celui-ci est trop élevé.
  • L’action intégrale I: pour compenser l’erreur statique que l’action proportionnelle ne peut pas prendre en charge, l’action intégrale intègre cette erreur pendant un temps Ti, le résultat est ensuite ajouté à la commande. Plus cette action est importante, plus les oscillations autour de la consigne seront accentuées.
  • L’action dérivé D: cette action dérive l’erreur pendant un temps Td, cela permet de diminuer les dépassements et le temps de stabilisation mais est très sensible au bruit.

Un peu de mathématiques pour déterminer l'algorithme du PID numérique

Les calculs suivants traiteront de la forme parallèle du contrôleur PID comme on vient de voir. Il peut paraître fastidieux, j'ai donc essayé de simplifier. Ce qu'il faut vraiment retenir c'est le résultat final et la signification de chaque terme de l'équation.


Pour un contrôleur PID parallèle, l'équation à temps continu s’écrit:
u(t) et ε(t) représentent respectivement le signal de commande et d’erreur à l’instant t. K est le gain proportionnel, Ti le temps d’intégration et Td le temps de dérivation.

A temps discret l’intégrale et la dérivée deviennent:

Te est la période d’échantillonnage.

D’où l’équation du PID à temps discret:
Ce qui permet de définir les paramètres Kp, Ki et Kd en fonction de la période d’échantillonnage:

Maintenant revenons sur l’équation du début à temps continu. Sa transformée de Laplace s'écrit:
L’équivalent à temps discret de la transformée de Laplace est la transformée en Z, ce qui nous donne:
Si on prend en compte les paramètres Kp , Ki et Kd décrits précédemment on aura donc:
En réarrangeant:
Ou encore:

Avec

 En mettant sous la forme d’'une équation aux différences on obtient:
u[k] est la valeur de la commande à l'instant k, elle est calculée en fonction des valeurs de commande et d'erreur à des instants passés représentés par k-1 et k-2.
Cette forme d’équation devient alors facilement implémentable en logique programmable par l'utilisation de bascule D, multiplieurs et additionneurs. 

Schéma bloc du contrôleur PID parallèle numérique:



Programme VHDL

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity PID_simple is
port( clk : in std_logic;
set_point : in signed(15 downto 0); --signal de commande
measure : in signed(15 downto 0); --signal de control
K1,K2,K3 : signed(7 downto 0) := (others => '0'); --parametre PID
PIDout : inout signed(23 downto 0));  --sortie asservissement
end PID_simple;
architecture behavior of PID_simple is
signal error_prev1, error_prev2, error : signed(15 downto 0) := (others => '0');
signal u_prev, add_out : signed(23 downto 0) := (others => '0');
begin
error <= set_point - measure; --calcul de l erreur
add_out <= u_prev + K1*error + K2*error_prev1 + K3*error_prev2; --equation aux differences
process(clk) --retard
begin
if(rising_edge(Clk)) then
error_prev1 <= error;
error_prev2 <= error_prev1;
u_prev <= add_out;
PIDout <= add_out;
end if;
end process;
end behavior;

samedi 14 septembre 2013

Hacheur pour moteur DC

On peut alimenter le moteur et ajuster sa vitesse de rotation en variant le rapport cyclique α du hacheur PWM. Pour être efficace, la fréquence de hachage de ce dernier doit être choisit correctement.

Soit le hacheur en série au borne d'un moteur à courant continu:

On obtient les signaux suivants:
Où I est le courant traversant le moteur

Le calcul de l’ondulation du courant aux bornes du moteur nous donne :
Avec L l’inductance interne du moteur et f la fréquence du hacheur. Notons que la résistance interne du moteur peut être négligée.

Cette valeur est maximal quand α=0,5 d’où :
On essaiera de diminuer ∆imax pour éviter d’avoir des ondulations trop importantes. Pour cela on peut ajouter une inductance en série avec le moteur qui permettra de lisser le courant à ses bornes ou encore augmenter la fréquence de hachage.

vérification expérimentale en cours...

mercredi 11 septembre 2013

Un PWM en VHDL

Un exemple de PWM (Pulse With Modulation) en VHDL. Le rapport cyclique du signal de sortie est contrôlé à l'entrée duty_cycle de 0 à 100% par pas de 10.



library ieee;
use ieee.std_logic_1164.all;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity pwm is port (
      clk: in std_logic;
      SW : in std_logic_vector (3 downto 0); --rapport cyclique (de 0 à 10) 
      reset : in std_logic;
      GPIO_0 : out std_logic_vector(35 downto 0)); -- sortie
end pwm;

architecture behavior of pwm is

signal cnt : std_logic_vector(3 downto 0) := "0000";
signal s : std_logic;

begin
process (clk,rst) begin
  if reset ='1' then s<=(others=>'0');
  elsif (clk'event and clk='1') then
    if (cnt < SW) then s <='1'; --met la sortie à 1 jusqu'a 
    else s <='0';               --la valeur du rapport cyclique
    end if;
    if (cnt >= "1001") then cnt<="0000"; --remet le compteur à 0 
    else cnt <= cnt + 1;                 --quand on a compté jusqu'a 10
    end if;
  end if;
end process;
GPIO_0(29) <= s;
end behavior;

Simulation sous ModelSim


Le circuit L298

Comparé au circuit utilisant le relais mécanique, avec le circuit L298 ou Dual Full-Bridge Driver, on a un pilotage assez aisé des moteurs. Il intègre 2 ponts en H, idéal pour chaque moteur. Le pont en H a pour but de changer le sens de rotation en mettant à l'état logic '1' l'une ou l'autre des entrées dédiées IN1 et IN2. Une entrée Enable autorise ou non le contrôle du pont. On peut ainsi adjoindre un signal PWM pour faire varier la vitesse de rotation en hachant la tension l'alimentation. Cela devient donc plus simple pour un microcontrôleur.

Schéma électronique du pont en H


Lorsque ENABLE = '1', si IN1 = '1' et IN2 = '0' les transistors T1 et T4 sont saturés laissant passer le courant dans le moteur dans un sens, T2 et T3 sont bloqué. Si maintenant IN1 = '0' et IN2 = '1', c'est T2 et T3 qui laisse passer le courant dans l'autre sens. Pour IN1 et IN2 dans le même état logique, on a affaire à un freinage moteur.

Schéma de montage du L298


Les 4 diodes aux bornes du moteur sont des diodes rapides ou diodes de Schottky. Elles jouent principalement le rôle de diode de roue libre pour l'inductance interne du moteur afin de protéger le circuit des surtensions lors du freinage.
On inclut dans le montage des capacités de découplage. Ces capacités, placées au plus près de l'alimentation des circuits numériques vont supprimer les signaux hautes fréquences parasites qui pourraient remonter le long de la ligne d'alimentation, par exemple lors du hachage créé par le PWM.
Une résistance placée entre la masse et le circuit de puissance permet de déterminer le courant traversant le moteur par la loi d'Ohm en mesurant la tension à ses bornes. Elle sert de shunt de mesure.
Pour plus de détails sur le L298, consulter le datasheet.


Voici le L298, une modification a été apportée au niveau des pattes car les dimensions ne sont pas adaptées à la platine d'essai. Il est aussi nécessaire d'ajouter un dissipateur thermique par rapport au courant qui peu monter jusqu'à 1A voir 2A pour les deux moteurs en service.

mardi 10 septembre 2013

Modélisation 3D de la table

DAO sous Inventor du plotter

J'ai pu terminer de dessiner l'ensemble de la table à quelques détails près, les courroies de transmission et les vis ne figurent pas encore. Les différentes pièces à fabriquer ont du être adapter avec celles qui existaient déjà ainsi qu'aux contraintes liées à l'usinage. Il ne manque plus que le dessin technique, l'usinage et l'assemblage pour cette partie mécanique.

Envoi des coordonnées vers la carte

Dans la vidéo ci dessous, je montre comment piloter le moteur en lui envoyant des coordonnées depuis un ordinateur via le port série



Description :

Après avoir réalisé le programme et compilé, il est ensuite envoyé vers la carte. Je fais la prise d'origine et remets le compteur à zéro. Le test permet juste de vérifier la communication par l'allumage des LEDs. Les huit LEDs vertes correspondent au code ASCII envoyé en appuyant sur les touches du clavier. Le déclenchement de l'acquisition des coordonnées se fait uniquement si la touche A à été appuyé en premier. Les valeurs sont ensuite stockées dans une adresse mémoire identifiable par les deux dernières LEDs rouges. Après avoir entré la coordonnée, la touche "entrée" valide puis envoie la commande de déplacement du moteur. On peut voir sur l'affichage 7 segments que le chariot arrive à une valeur assez proche mais pas exacte, cela est du au fait que le chariot à pris de la vitesse et ne s'arrêtera pas tout de suite même si le moteur est coupé. Un système d'asservissement en position sera donc nécessaire pour la suite.

Communication PC vers carte FPGA via RS-232

Schéma synoptique du fonctionnement globale


Tout ce qui est en gris reste à programmer.


Détails sur le protocole de communication

La communication du PC vers la carte est pour l'instant assez simple, il suffit d'entrer un premier message, le drapeau ou trame de début pour amorcer l'acquisition des coordonnées à 4 chiffres et une trame de fin.

 


Les trames de début et de fin sont définies sur 8 bits, dans la vidéo que j'ai présenté, il s'agit respectivement du caractère "A" et "retour à la ligne" en codage ASCII. Pour les données utiles, seul les 4 premiers bits sont nécessaires pour chacun des chiffres. Ces valeurs sont ensuite comparées à la position du chariot (voir codeur optique) dans un comparateur pour piloter le moteur dans un sens ou dans l'autre.


La machine à états




Ce diagramme décrit le processus d'acquisition des données. Les étapes pour les dizaine et unité ne sont pas mentionnées par souci de taille de l'image, c'est le même principe que les centaines et milliers .Notons que la mémoire n'est pas vraiment utile pour l'instant, je ne m'en suis pas vraiment servi dans ce programme, mais ce qui m'intéressait avant tout c'est la façon de transférer les données dans le bloque mémoire M4K de la puce. Je ne prétends pas que c'est la meilleur façon de procéder, je ne suis pas un expert en VHDL. Toutes autres solutions seraient bienvenue.

Dans la première étape, le système attend que la donnée data_in issu de l'uart RS232 correspond à la lettre "A" en ASCII avant de passer à l'étape d'acquisition du chiffre des milliers, data_done est un signal qui permet de valider la transmission complète de la trame du port série. Avant chaque écriture dans la mémoire grâce au signal d'horloge memory_clock, on incrémente l'adresse mémoire pour ne pas écraser la donnée précédente. L'étape task permet d'activer le moteur en fin de trame en attendant que le déplacement se termine avant de retourner à l'état initial.

Programme VHDL

en construction...

RS-232 en VHDL

Présentation
Le port série ou RS-232 est un bus de communication qui permet de transférer des données en série sur deux fils, TxD (Tansmission Data) et RxD(Reception Data) en utilisant un UART (Universal Asynchronous Receiver Transmitter).

Protocole de communication
Pour la bonne transmission des données il est nécessaire que l'émetteur et le récepteur utilisent le même protocole.
Composition de la trame:

  • 1 bit de start :  ce bit correspond à un niveau logique 0, il permet la synchronisation de l'horloge du recepteur
  • 5 à 8 bits de données : ce sont les données à transmettre
  • 0 ou 1 bit de parité : pas obligatoire, ce bit permet de verifier la présence d'erreurs
  • Bit(s) de stop : on retourne au niveau logique 1 qui est l'état de repos de la ligne
Le debit de la liaison RS232 va de 110 à 115200 baud.


Programme VHDL
Dans ce programme nous travaillerons avec un débit de 19 200 baud. Ce paramètre peut être ajusté directement dans le programme. Pour plus de détails sur le fonctionnement consulter Design Example : UART pdf
  • Composant générateur d'horloge d'échantillonnage:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity mod_m_cnter is
 generic(
    N: integer := 8;
    M: integer := 163);
 port(
   clk, reset: in std_logic;
   clk_cnt: out std_logic);
end mod_m_cnter;

architecture arch of mod_m_cnter is
signal r_reg: unsigned(N-1 downto 0);
signal r_next: unsigned(N-1 downto 0);
begin
 process(clk, reset)
 begin
  if (reset = '0') then
   r_reg <= (others => '0');
  elsif (clk'event and clk='1') then
   r_reg <= r_next;
  end if;
 end process;
-- next state logic
r_next <= (others => '0') when r_reg=(M-1) else r_reg + 1;
clk_cnt <= '1' when r_reg=(M-1) else '0';
-- output logic
end arch;
  • Composant UART Rx
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity uart_rx is
generic(
   DBIT: integer := 8;
   SB_TICK: integer := 16 -- (16, 24 and 32 for 1, 1.5 and 2 stop bits)
   );
port(
  reset, clk: in std_logic;
  rx: in std_logic;
  s_tick: in std_logic;
  rx_done_tick: out std_logic;
  dout: out std_logic_vector(7 downto 0)
  );
end uart_rx;


architecture arch of uart_rx is
type state_type is (idle, start, data, stop);
signal state_reg, state_next: state_type;
signal s_reg, s_next: unsigned(3 downto 0);
signal n_reg, n_next: unsigned(2 downto 0);
signal b_reg, b_next: std_logic_vector(7 downto 0);

begin
process(clk, reset) -- FSMD state and data regs.
 begin
  if (reset = '0') then
  state_reg <= idle;
  s_reg <= (others => '0');
  n_reg <= (others => '0');
  b_reg <= (others => '0');
  elsif (clk'event and clk='1') then
  state_reg <= state_next;
  s_reg <= s_next;
  n_reg <= n_next;
  b_reg <= b_next;
  end if;
end process;

-- next state logic
process (state_reg, s_reg, n_reg, b_reg, s_tick, rx)
begin
state_next <= state_reg;
s_next <= s_reg;
n_next <= n_reg;
b_next <= b_reg;
rx_done_tick <= '0';


 case state_reg is
   when idle =>
   if (s_tick = '1') then
    if (rx = '0') then
    state_next <= start;
    s_next <= (others => '0');
    end if;
   end if;

   when start =>
    if (s_tick = '1') then
     if (s_reg = 7) then
      state_next <= data;
      s_next <= (others => '0');
      n_next <= (others => '0');
     else
      s_next <= s_reg + 1;
     end if;
    end if;

   when data =>
    if (s_tick = '1') then
     if (s_reg = 15) then
     s_next <= (others => '0');
     b_next <= rx & b_reg(7 downto 1);
      if (n_reg = (DBIT - 1)) then
      state_next <= stop;
      else
      n_next <= n_reg + 1;
      end if;
     else
     s_next <= s_reg + 1;
     end if;
    end if;
   when stop =>
    if (s_tick = '1') then
     if (s_reg >= (SB_TICK-1)) then
     state_next <= idle;
     rx_done_tick <= '1';
     else
     s_next <= s_reg + 1;
     end if;
    end if;
 end case;
end process;

dout <= b_reg;
end arch;


  • Composant UART Tx
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity uart_tx is
 generic(
    DBIT: integer := 8;
    SB_TICK: integer := 16
    );
 port(
    reset,clk: in std_logic;
    tx_start: in std_logic;
    s_tick: in std_logic;
    din: in std_logic_vector(7 downto 0);
    tx_done_tick: out std_logic;
    tx: out std_logic
    );
end uart_tx;


architecture arch of uart_tx is
type state_type is (idle, start, data, stop);
signal state_reg, state_next: state_type;
signal s_reg, s_next: unsigned(3 downto 0);
signal n_reg, n_next: unsigned(2 downto 0);
signal b_reg, b_next: std_logic_vector(7 downto 0);
signal tx_reg, tx_next: std_logic;
begin

 process(clk, reset) -- FSMD state and data regs.
 begin
  if (reset = '0') then
  state_reg <= idle;
  s_reg <= (others => '0');
  n_reg <= (others => '0');
  b_reg <= (others => '0');
  tx_reg <= '1';
  elsif (clk'event and clk='1') then
  state_reg <= state_next;
  s_reg <= s_next;
  n_reg <= n_next;
  b_reg <= b_next;
  tx_reg <= tx_next;
  end if;
end process;

-- next state logic
 process (state_reg, s_reg, n_reg, b_reg, s_tick,tx_reg, tx_start, din)
 begin
 state_next <= state_reg;
 s_next <= s_reg;
 n_next <= n_reg;
 b_next <= b_reg;
 tx_next <= tx_reg;
 tx_done_tick <= '0';

  case state_reg is
    when idle => tx_next <= '1';
     if (tx_start= '1') then
      state_next <= start;
      s_next <= (others => '0');
      b_next <= din;
     end if;
    when start => tx_next <= '0';
     if (s_tick = '1') then
      if (s_reg = 15) then
      state_next <= data;
      s_next <= (others => '0');
      n_next <= (others => '0');
      else
      s_next <= s_reg + 1;
      end if;
     end if;
    when data => tx_next <= b_reg(0);
     if (s_tick = '1') then
      if (s_reg = 15) then
      s_next <= (others => '0');
      b_next <= '0' & b_reg(7 downto 1);
       if (n_reg = (DBIT - 1)) then
       state_next <= stop;
       else
       n_next <= n_reg + 1;
       end if;

      else
      s_next <= s_reg + 1;
      end if;
     end if;
    when stop => tx_next <= '1';
     if (s_tick = '1') then
      if (s_reg = (SB_TICK-1)) then
      state_next <= idle;
      tx_done_tick <= '1';
      else
      s_next <= s_reg + 1;
      end if;
     end if;
  end case;
end process;
tx <= tx_reg;
end arch;


  • Composant générateur d'impulsion de durée réglable
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.std_logic_unsigned.all;

entity pulse is
  generic(
          lenght: integer := 159 --clock period
          );
  port(                
        clock,Key :in std_logic;
        s :out std_logic
        );
end;

architecture behavior of pulse is
  type state_type is (idle, impulse, stop);
  signal state : state_type;
  signal count : integer := 0;

  begin
    process (clock,state)
  begin
         if (clock'EVENT and clock ='1')  then
         
                case state is
    
                  when idle => s<='0';
       if key ='1' then state <= impulse;
       else state <= idle;
       end if;
                  when impulse => s<='1';
         if count=lenght then state <= stop;
       else count<=count+1;
       end if; 

                  when stop => s<='0'; count<=0;
            if Key ='0' then state <= idle;
                  else state <= stop;
                  end if;
             end case;
          end if;

    end process;
end behavior;


  • Composant registre
library ieee;
use ieee.std_logic_1164.all;

entity registre is
port (d_out : in std_logic_vector(7 downto 0);
enable, reset : in std_logic;
out_d : out std_logic_vector(7 downto 0));
end;

architecture arch of registre is
begin
process (enable)
begin
  if (reset = '0') then
  out_d <= (others=>'0');
  elsif (enable'event and enable='1') then out_d <= d_out;
  end if;
end process;
end arch;

  • Fichier principal : module RS232
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity module_RS232 is
port (
  uart_rxd :  in std_logic;
  uart_txd :  out std_logic;
  KEY : in std_logic_vector(1 downto 0);
  SW : in std_logic_vector(7 downto 0);
  CLOCK_50 : in std_logic;
  LEDG : out std_logic_vector(7 downto 0));
end;


architecture behavior of module_RS232 is
---------------------------------------------------
component mod_m_cnter
generic(
N: integer := 8;
M: integer := 163);
port(
clk, reset: in std_logic;
clk_cnt: out std_logic
);
end component;
---------------------------------------------------
component uart_rx is
generic(
DBIT: integer := 8;
SB_TICK: integer := 16 -- (16, 24 and 32 for 1, 1.5 and 2 stop bits)
);
port(
clk, reset: in std_logic;
rx: in std_logic;
s_tick: in std_logic;
rx_done_tick: out std_logic;
dout: out std_logic_vector(7 downto 0));
end component;
------------------------------------------------------
component uart_tx is
generic(
DBIT: integer := 8;
SB_TICK: integer := 16);

port(
reset,clk: in std_logic;
  tx_start: in std_logic;
  s_tick: in std_logic;
  din: in std_logic_vector(7 downto 0);
  tx_done_tick: out std_logic;
  tx: out std_logic
);
end component;

-------------------------------------------------PULSE
component pulse IS
  generic(
          lenght: integer := 159 --clock period
          );
  PORT(                
        clock,Key :IN std_logic;
        s :OUT std_logic
        );
END component;

-------------------------------------------------

signal wire_clk_cnt, s_tick, wire_rx_done ,wire_tx_done, wire_reg_enable, wire_pulse : std_logic;
signal wire_dataout: std_logic_vector(7 downto 0);


begin

I1 : mod_m_cnter port map (clk=>CLOCK_50, reset=>(KEY(0)), clk_cnt=>wire_clk_cnt);
I2 : uart_rx port map (clk=>CLOCK_50, s_tick=>wire_clk_cnt, reset=>(KEY(0)), rx=>uart_rxd, dout=>wire_dataout, rx_done_tick=>wire_reg_enable);
I3 : uart_tx port map (clk=>CLOCK_50, s_tick=>wire_clk_cnt, reset=>(KEY(0)), tx=>uart_txd, din=>SW,tx_done_tick=>wire_tx_done, tx_start=>wire_pulse);
I4 : registre port map (reset=>(KEY(0)), d_out=>wire_dataout, out_d =>LEDG, enable=>wire_reg_enable);
I5 : pulse port map(clock => CLOCK_50, Key => not(KEY(1)), s => wire_pulse);
end behavior;




Quelques modifications ont été apportées pour le générateur d'horloge d'échantillonage et pour l'UART ou la machine à état s'arrête parfois à cause d'un défaut de synchronisation du signal reçu. Des circuits ont été supprimés pour plus de simplicité et un générateur d'impulsion réglable a été ajouté pour envoyer les données une à une vers l'ordinateur. Lorsque l'on appuie sur le bouton pour émettre, on applique sur l'entrée tx_data une impulsion de même durée que la trame de donnée afin de s'assurer que toute cette trame à bien été envoyée.

Electronique de pilotage moteur à relais électromécanique

Pour le moteur à courant continu, le contrôle est réalisé avec un simple relais en attendant d'avoir un circuit mieux adapté, un driver spécialisé tel que le L298.

Schéma de câblage :


Le pin 33 allume ou éteint le moteur alors que le 34 contrôle le sens selon que le niveau logique soit à 0 ou 1.

Encodeur en quadrature de phase en VHDL

Le signal de sortie d'un encodeur optique à la forme suivante:


Ce signal est intéressant car déphasé de 90° il permet de déterminer le sens de déplacement. En effet, si la voie A est en avance par rapport à la voie B on se déplace dans un sens, dans le cas contraire on se déplace dans l'autre sens.
Dans le programme VHDL, j'utilise une machine à états finis (FSM Finite State Machine) pour compter ou décompter en fonction du niveau du signal reçu.

Diagramme d'état

Le schéma suivant présente les états possibles en fonction du niveau logique issu du capteur optique. Chaque cycle peut compter jusqu'a 4 incréments ou décréments. Il est toujours possible de modifier le programme de façon à prendre en compte que 1 ou 2 incréments par cycle en fonction de la précision que l'on désire.


Le diagramme d'état:


Le diagramme d'état prend en compte toutes les transitions possibles, ce qui correspond par exemple à des cas ou le cycle ne s'est pas totalement terminé. Imaginons que l'on se trouve à D5, le compteur à déjà compté jusqu'a 3, si on recule avant d'arriver à D6, on passe à l'état G2 qui immédiatement décrémentera d'une unité.

Programme

library ieee;
use ieee.std_logic_1164.all;

entity FSM_encoder is
port (
                clk: in std_logic;
                init: in std_logic;
                wayA_B : in std_logic_vector(1 downto 0);
                en: out std_logic; -- enable
                du: out std_logic); --down/up
end FSM_encoder;


architecture machine_behavior of FSM_encoder is

type state is (idle,D0,D1,D2,D3,D4,D5,D6,G0,G1,G2,G3,G4,G5,G6);
signal next_state,reg_state:state;

begin

process(clk)
        begin
                if rising_edge(clk) then
                        if init='1' then reg_state<=idle;
                        else reg_state<=next_state;
                        end if;
                end if;
end process;
----
process (reg_state,wayA_B)
        begin
        case reg_state is

                        when idle=>
                                        if (wayA_B = "01") then next_state<=G0;
                                        elsif (wayA_B = "10") then next_state<=D0;
                                        else next_state<=idle;
                                        end if;

                        when D0=> next_state<=D1;
                        when D1=>
                                        if (wayA_B = "10") then next_state<=D1;
                                        elsif (wayA_B = "11") then next_state<=D2;
                                        elsif (wayA_B = "00") then next_state<=G6;
                                        else next_state<=idle;
                                        end if;
                        when D2=> next_state<=D3;
                        when D3=>
                                        if (wayA_B = "01") then next_state<=D4;
                                        elsif (wayA_B = "10") then next_state<=G4;
                                        elsif (wayA_B = "11") then next_state<=D3;
                                        else next_state<=idle;
                                        end if;
                        when D4=> next_state<=D5;
                        when D5=>
                                        if (wayA_B = "00") then next_state<=D6;
                                        elsif (wayA_B = "01") then next_state<=D5;
                                        elsif (wayA_B = "11") then next_state<=G2;
                                        else next_state<=idle;
                                        end if;
                        when D6=> next_state<=idle;

                        when G0=> next_state<=G1;
                        when G1=>
                                        if (wayA_B = "01") then next_state<=G1;
                                        elsif (wayA_B = "11") then next_state<=G2;
                                        elsif (wayA_B = "00") then next_state<=D6;
                                        else next_state<=idle;
                                        end if;
                        when G2=> next_state<=G3;
                        when G3=>
                                        if (wayA_B = "01") then next_state<=D4;
                                        elsif (wayA_B = "10") then next_state<=G4;
                                        elsif (wayA_B = "11") then next_state<=G3;
                                        else next_state<=idle;
                                        end if;
                        when G4=> next_state<=G5;
                        when G5=>
                                        if (wayA_B = "00") then next_state<=G6;
                                        elsif (wayA_B = "10") then next_state<=G5;
                                        elsif (wayA_B = "11") then next_state<=D2;
                                        else next_state<=idle;
                                        end if;
                        when G6=> next_state<=idle;
        end case;
end process;

--down/up assignement
en<='1' when (reg_state=D0 or reg_state=D2 or reg_state=D4 or reg_state=D6 or reg_state=G0 or reg_state=G2 or reg_state=G4 or reg_state=G6) else '0';
du<='1' when (reg_state=D0 or reg_state=D2 or reg_state=D4 or reg_state=D6) else '0';

end machine_behavior;
Ce programme est utilisé dans le cas ou la sortie est connectée à des compteurs décimaux mise en cascade. La sortie en(enable) valide le comptage si sa valeur est '1' et du(down/up) détermine si on incrémente ou décrémente suivant sa valeur '0' ou '1'.

Voici un extrait de la simulation sous ModelSim avec l'exemple énoncé précédemment. On remarque bien que la valeur de du passe à '0' quand on revient vers la gauche momentanément.
(cette image est a mettre à jour car on ne distingue pas bien les noms des signaux)