Very High Speed Integrated Circuit Hardware Description Language/Commande de robot mobile et périphériques associés
Nous allons nous intéresser dans ce chapitre à la commande de robots différentiels mobiles par une carte FPGA. L’idée est naturellement d’utiliser un processeur pour les calculs mais aussi de développer des périphériques associés. La platine robotique utilisée est celle de Digilent car elle est adaptée pour une commande en 3,3 V.
Introduction
[modifier | modifier le wikicode]La robotique mobile qui nous intéresse dans ce chapitre, est constitué par des petits robots propulsés en général à l'aide de deux roues. Ce type de robot est appelé "robot mobile différentiel", mais par souci de simplification, nous omettrons souvent le terme "différentiel". Voici quelques exemples de robots mobiles différentiels sur l'image ci-dessous.

Vous voyez sur cette image des robots mobiles plus ou moins sophistiqués qui sont en général commandés par un micro-contrôleur. Dans ce chapitre nous allons nous intéresser essentiellement à remplacer le micro-contrôleur de commande par un FPGA. Ce n’est pas le prix qui nous attire dans cette voie (les FPGA restent des circuits chers par rapport aux micro-contrôleurs). Ce qui nous intéresse est l'étude de périphériques conçus maison pour propulser un robot et l'aider à se repérer dans l'espace.
Le robot Asuro, à gauche dans la photo ci-dessus, a été étudié en détail dans le chapitre d'un autre livre : AVR et robotique : ASURO. La compréhension de l'odométrie du présent chapitre nécessitera la lecture de la section Étude de la cinématique d'un robot différentiel de cet autre projet.
Remplacer un micro-contrôleur par un FPGA ne pose en général que quelques problèmes sur les voltages. La famille Arduino par exemple gère des tensions de 5 V à part le "DUE". Par contre les FPGAs sont depuis longtemps déjà en 3,3 V. Pour éviter tout problème à ce niveau, nous avons choisi un Robot de chez Digilent car cette entreprise réalise à la fois des cartes FPGA et des cartes micro-contrôleur. On aura une compatibilité électrique entre les deux. Présentons maintenant cette base robotique.
Présentation de la platine Digilent
[modifier | modifier le wikicode]La platine de robotique mobile de Digilent nous semple être un très bon point de départ pour un robot évolutif. Il est assez facile d'ajouter des modules extérieurs (et en particulier les fixer). La carte de commande originale utilise un PIC32 (CEREBOT MX4ck) mais nous avons retiré cette carte pour la remplacer par une carte FPGA. La Basys2 plus petite en taille que cette carte se fixe parfaitement sur le robot mais la Nexys3 peut certainement faire aussi l'affaire.
Nous avons à disposition Line-Following Motor Robot Kit mais il nous semble que MRK-BASIC moins chère peut suffire si vous n'avez pas l'intention de suivre des lignes.
Cette platine contient une partie mécanique évolutive, deux commandes de puissance pour deux moteurs à courant continu, et un éventuel détecteur infra-rouge capable de gérer 4 photo-transistors.
La carte de commande est architecturée autour d'un micro-contrôleur PIC32. Elle ne sera pas détaillée ici puisqu'elle sera remplacée de toute façon par une carte FPGA.
Depuis quelques années Digilent développe des modules d'interface aux capteurs appelés PMOD. L'intérêt de ceux-ci est qu’ils s'interfacent facilement aux cartes à micro-contrôleurs comme aux cartes FPGA. Ils sont décrits brièvement dans le chapitre Utiliser un processeur externe de manière pratique.
La gestion de la détection infra-rouge
[modifier | modifier le wikicode]Il s'agit d'un module pouvant gérer 4 photo-transistors infrarouge avec le standard pmod de Digilent : module pmodLS1. L'intérêt de ces modules, comme déjà évoqué, est qu’ils se connectent sur les cartes micro-contrôleurs comme sur les cartes FPGA modernes (Basys2, Nexys2, Nexys3,... et bien d'autres encore). Ce module demande à être calibré à l'aide d'un réglage de potentiomètre.
Pour calibrer, nous vous conseillons d’utiliser un scotch noir sur un fond blanc (en bois) et de mettre le robot dans différentes positions par rapport au scotch. Des leds présentes sur le module doivent s'allumer et s'éteindre suivant la position.
Ces modules sont présentés plus loin.
Nous allons poursuivre par la présentation de la commande de puissance des deux moteurs.
Les modules de commande de moteur : PmodHB5
[modifier | modifier le wikicode]Les moteurs à commander sont à courant continu (MT-MOTOR). Le module Pmod qui se charge de la commande en puissance s’appelle PmodHB5.
La description des PmodHB5 est disponible sur le site de Digilent et pour ceux qui sont allergiques à l'anglais, une description sommaire est disponible chez Lextronic.
Les moteurs ont deux codeurs incrémentaux à disposition. Le lien déjà présenté, (mais rappelé ici : MT-MOTOR), montre sur la photo à droite des codeurs incrémentaux. Nous allons les présenter brièvement maintenant.
Les codeurs incrémentaux
[modifier | modifier le wikicode]
La théorie de ces codeurs est représentée ci-contre. On vous représente deux signaux appelés phases qui sont déphasés. Ce qui caractérise ces signaux est que le déphasage dépend du sens de rotation. La figure représente ces deux signaux pour un sens de rotation dans le sens des aiguilles d'une montre. A est alors en retard de 90°.
Dans le cas d'une rotation dans l'autre sens, A sera en avance de 90° sur B. Ces informations conduisent à une description de suite d'états pour les deux sens de rotation.
Les deux diagramme d'états correspondant sont
|
|
L'exploitation de ces codeurs en tant que périphérique fera appel à ces données et sera présentée plus loin dans ce chapitre.
Interfacer le robot à la carte FPGA
[modifier | modifier le wikicode]Remplacer la platine de commande originale par un FPGA ne pose absolument aucun problème particulier. Pour éviter une trop longue description verbale, nous vous demandons de vous reporter aux quelques images du robot du chapitre.
Commander les moteurs du robot avec notre FPGA
[modifier | modifier le wikicode]Nous allons utiliser un processeur pour prendre les décisions qui s'imposent en fonction des données des capteurs. Le processeur enfoui dans notre FPGA sera l'ATMega16 déjà décrit dans ce livre.
Vous pouvez remarquer sur la photo ci-dessus, que l’on à remplacé le support fixe par une bille permettant un roulement plus facile. Une roue castor libre pourrait tout aussi bien faire l'affaire.
Les modules de commande de moteur : pmodHB5
[modifier | modifier le wikicode]Nous avons décidé d’utiliser le module HB5RefComp fourni par Digilent dont voici l'entité en VHDL :
entity HB5RefComp is
Port ( ck : in STD_LOGIC; -- system clock ({{unité|25|MHz}})
bitDirIn : in STD_LOGIC; -- User direction request
vecDfIn : in STD_LOGIC_VECTOR(7 downto 0); -- Duty factor value
bitEnOut : out STD_LOGIC:= '0'; -- Enable pin for Pmod HB5
bitDirOut : out STD_LOGIC:= '1'); -- Dir pin for Pmod HB5
end HB5RefComp;
Ce module s'occupe de générer "bitEnOut" et "bitDirout" nécessaires à la commande de l'électronique de puissance à partir d'une horloge (à 25 MHz pour nous) d'un bit de direction "bitDirIn" et d'un rapport cyclique "vecDfIn" sur 8 bits.
Le module HB5RefComp
[modifier | modifier le wikicode]Comme nous avons changé quelques options par rapport au fichier original téléchargé, nous donnons sa version intégrale maintenant.
----------------------------------------------------------------------------------
-- Company: Digilent RO
-- Engineer: Mircea Dabacan
--
-- Create Date: 15:30:29 03/02/2008
-- Module Name: HB5RefComp - Behavioral
-- Project Name: Hb5RefProj
-- Description: This is the simple Digilent HB5 Reference Component.
-- This component can also be used with PmodHB3.
-- Inputs:
-- - ck = clock signal ({{unité|100|MHz}})
-- - bitDirIn = the direction to rotate the motor
-- - vecDfIn = the value of the duty factor for the PWM signal.
-- Outputs:
-- - bitEnOut = Enable pin for Pmod HB5
-- - bitDirOut = Dir pin for Pmod HB5
--
-- Internal constants:
-- - intPreDiv = Pre division factor. Defines the division factor
-- used to build the internal clock (ckInt).
-- frequency(ckInt) = frequency(ckInt)/intPreDiv.
-- - strRadix = string to define the radix used for vecDfIn and
-- internal counter. Allowed values:
-- - "Bcd" for BCD (the whole PWM circuit works in BCD)
-- - "Hex" for hexadecimal (the whole PWM circuit works in hexadecimal)
-- - intSwDelay = number of ck periods to force bitEnOut "low" before and after
-- a state change of bitDirOut.
-- Behavioral description:
-- bitEnOut is built as a PWM signal.
-- The frequency of bitEnOut is:
-- - frequency(ckInt)/256 - if strRadix is "Hex"
-- - frequency(ckInt)/100 - if strRadix is "Bcd"
-- The Duty Factor of bitEnOut is:
-- - vecDfIn/256 - if strRadix is "Hex"
-- - vecDfIn/100 - if strRadix is "Bcd"
--
-- To avoid Pmod HB5 damage, bitDirOut is not allowed to change state when
-- bitEnOut is "high" or even close to that.
-- To make sure of this condition, when the bitDirIn change state,
-- bitEnOut is forced "low" for intSwDelay ck periods, then bitDirOut gets
-- the value of bitDirIn, bitEnOut is forced "low" for other intSwDelay ck
-- periods before being released to generate further PWM cycles.
-- Dependencies:
--
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
Library UNISIM;
use UNISIM.vcomponents.all;
entity HB5RefComp is
Port ( ck : in STD_LOGIC; -- system clock ({{unité|25|MHz}})
bitDirIn : in STD_LOGIC; -- User direction request
vecDfIn : in STD_LOGIC_VECTOR(7 downto 0); -- Duty factor value
bitEnOut : out STD_LOGIC:= '0'; -- Enable pin for Pmod HB5
bitDirOut : out STD_LOGIC:= '1'); -- Dir pin for Pmod HB5
end HB5RefComp;
architecture Behavioral of HB5RefComp is
-- constant intPreDiv: integer :=2048; -- predivision factor
constant intPreDiv: integer :=512; -- because {{unité|25|MHz}}
-- with intPreDiv = 2048:
-- frequency(ckInt) = {{unité|100|MHz}}/2048 = 48.83KHz
-- frequency(bitEnOut) = {{unité|100|MHz}}/2048/100 = 488.{{unité|3|Hz}} (with strRadix = BCD)
-- frequency(bitEnOut) = {{unité|100|MHz}}/2048/256 = 190.{{unité|7|Hz}} (with strRadix = HEX)
-- constant strRadix: string := "BCD"; -- used radix ("BCD" or "HEX")
constant strRadix: string := "HEX";
constant intSwDelay: integer := 100000; -- # of ck periods to force bitEnOut "low"
-- before and after a change of bitDirOut state.
signal cntPreDiv: integer range 0 to intPreDiv - 1 := 0; --counter for predivider
signal ckInt, ckIntBuf: STD_LOGIC; -- Internal clock for PWM
signal cntSwDelay: integer range 0 to 2*intSwDelay := 0; --counter for delay
signal cntPwm: STD_LOGIC_VECTOR(7 downto 0) := x"00"; -- counter for PWM
signal bitPwmInt: STD_LOGIC; -- Internal Pwm signal (before delay protection)
signal bitDirOld: STD_LOGIC; -- old value of bitDirIn, to track changes
begin
-- BUFG: Global Clock Buffer (source by an internal signal)
-- Spartan-3E
-- Xilinx HDL Language Template, version 13.1
BUFG_inst : BUFG
port map (
O => ckIntBuf, -- Clock buffer output
I => ckInt -- Clock buffer input
);
preDiv: process(ck) -- clock predivision
begin
if ck'event and ck = '1' then
if cntPreDiv = intPreDiv - 1 then
cntPreDiv <= 0;
ckInt <= '1';
else
cntPreDiv <= cntPreDiv + 1;
ckInt <= '0';
end if;
end if;
end process;
PWM: process(ckIntBuf) -- PWM counter
begin
if ckIntBuf'event and ckIntBuf = '1' then
if strRadix = "HEX" then -- hexadecimal count
cntPwm <= cntPwm + '1';
elsif strRadix = "BCD" then -- decimal rollover
if cntPwm(3 downto 0) = x"9" then
cntPwm(3 downto 0) <= x"0";
if cntPwm(7 downto 4) = x"9" then
cntPwm(7 downto 4) <= x"0";
else
cntPwm(7 downto 4) <= cntPwm(7 downto 4) + x"1";
end if;
else
cntPwm(3 downto 0) <= cntPwm(3 downto 0) + x"1";
end if;
else -- wrong value of strRadix
cntPwm <= x"FF"; -- cntPwm stuck at 255, as result
-- bitPwmInt is forced permanently "low"
end if;
end if;
end process;
bitPwmInt <= '1' when cntPwm < vecDfIn else -- internal PWM signal
'0';
trackDir: process(ck) -- tracking bitDirIn and generating delay
begin
if ck'event and ck = '1' then
bitDirOld <= bitDirIn; -- tracking bitDirIn
if bitDirOld /= bitDirIn then -- bitDirIn changed
cntSwDelay <= 0; -- start delay
elsif cntSwDelay = 2*intSwDelay then -- delay expired
null; -- cntSwDelay stuck
else
cntSwDelay <= cntSwDelay + 1; -- counting delay
end if;
end if;
end process;
bitEnOut <= bitPwmInt when cntSwDelay = 2*intSwDelay else -- normal operation
'0'; -- during switching delay
switchDirection: process(ck) -- switching motor direction
begin
if ck'event and ck = '1' then
if cntSwDelay = intSwDelay then -- midd delay
bitDirOut <= bitDirOld; -- switching direction
end if;
end if;
end process;
end Behavioral;
Comme nous avons deux moteurs à commander, il nous faudra utiliser ce module en double. Nous avons décidé d’utiliser PORTB pour le rapport cyclique du moteur gauche et PORTC pour celui du moteur droit. Les deux bits de direction seront ramenés sur DDRB :
- moteur droit est relié au bit b0 de DDRB
- moteur gauche est relié au bit b1 de DDRB
Avant de présenter le code source de io.vhd, nous présentons comme d'habitude le problème sous forme de schéma :

Interface des pmodHB5 avec l'ATMega16
[modifier | modifier le wikicode]Voici le fichier "io.vhd" modifié pour prendre en compte deux moteurs. Bien sûr les sorties LeftEnOut, LeftDirOut, RightEnOut, RightDirOut sont à prolonger jusqu'à la sortie du processeur et donc à gérer correctement dans l'ucf. Voici les parties du fichier ucf concernées :
####### Only for Basys2 ######## # NET "RightDirOut" LOC = "A9" | DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = JC1 NET "RightEnOut" LOC = "B9" | DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = JC2 #NET "PIO<82>" LOC = "A10" | DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = JC3 #NET "PIO<83>" LOC = "C9" | DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = JC4 # NET "LeftDirOut" LOC = "C12" | DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = JD1 NET "LeftEnOut" LOC = "A13" | DRIVE = 2 | PULLUP ; # Bank = 2, Signal name = JD2 #NET "PIO<86>" LOC = "C13" | DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = JD3 #NET "PIO<87>" LOC = "D12" | DRIVE = 2 | PULLUP ; # Bank = 2, Signal name = JD4
où l’on voit que le moteur droit est relié au connecteur JC tandis que le moteur gauche est relié au connecteur JD.
Et voici donc le fichier io.vhd comme promis :
-------------------------------------------------------------------------------
--
-- Copyright (C) 2009, 2010 Dr. Juergen Sauermann
--
-- This code is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This code is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this code (see the file named COPYING).
-- If not, see http://www.gnu.org/licenses/.
--
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
--
-- Module Name: io - Behavioral
-- Create Date: 13:59:36 11/07/2009
-- Description: the I/O of a CPU (uart and general purpose I/O lines).
--
-------------------------------------------------------------------------------
--
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity io is
port ( I_CLK : in std_logic;
I_CLR : in std_logic;
I_ADR_IO : in std_logic_vector( 7 downto 0);
I_DIN : in std_logic_vector( 7 downto 0);
I_PINB : in std_logic_vector( 7 downto 0);
I_RD_IO : in std_logic;
I_RX : in std_logic;
I_WE_IO : in std_logic;
-- Q_PORTB : out std_logic_vector( 7 downto 0);
Q_DOUT : out std_logic_vector( 7 downto 0);
Q_INTVEC : out std_logic_vector( 5 downto 0);
--> changed 1 downto 0 to 7 downto 0 : 2011/10/18
-- Q_PORTC : out std_logic_vector( 7 downto 0);
-- added
LeftEnOut, LeftDirOut, RightEnOut, RightDirOut : out std_logic;
Q_PORTD : out std_logic_vector( 7 downto 0);
--<
Q_TX : out std_logic);
end io;
architecture Behavioral of io is
component uart
generic(CLOCK_FREQ : std_logic_vector(31 downto 0);
BAUD_RATE : std_logic_vector(27 downto 0));
port( I_CLK : in std_logic;
I_CLR : in std_logic;
I_RD : in std_logic;
I_WE : in std_logic;
I_RX : in std_logic;
I_TX_DATA : in std_logic_vector(7 downto 0);
Q_RX_DATA : out std_logic_vector(7 downto 0);
Q_RX_READY : out std_logic;
Q_TX : out std_logic;
Q_TX_BUSY : out std_logic);
end component;
component HB5RefComp is
Port ( ck : in STD_LOGIC; -- system clock ({{unité|25|MHz}})
bitDirIn : in STD_LOGIC; -- User direction request
vecDfIn : in STD_LOGIC_VECTOR(7 downto 0); -- Duty factor value
bitEnOut : out STD_LOGIC:= '0'; -- Enable pin for Pmod HB5
bitDirOut : out STD_LOGIC:= '1'); -- Dir pin for Pmod HB5
end component;
signal U_RX_READY : std_logic;
signal U_TX_BUSY : std_logic;
signal U_RX_DATA : std_logic_vector( 7 downto 0);
signal L_INTVEC : std_logic_vector( 5 downto 0);
signal L_LEDS : std_logic;
signal L_RD_UART : std_logic;
signal L_RX_INT_ENABLED : std_logic;
signal L_TX_INT_ENABLED : std_logic;
signal L_WE_UART : std_logic;
--> added 2011/10/19
signal baud_clk : std_logic;
--<
--> timer
signal pseudotimer : std_logic_vector(19 downto 0); --environ {{unité|100|Hz}}
signal L_TOIE0, L_TOV0 : std_logic; -- pour valider interruption timer
signal s_vecDfIn_Gauche, s_vecDfIn_Droite : std_logic_vector(7 downto 0);
signal s_RightDirIn, s_LeftDirIn : std_logic;
begin
urt: uart
generic map(CLOCK_FREQ => std_logic_vector(conv_unsigned(50000000, 32)),
BAUD_RATE => std_logic_vector(conv_unsigned( 115200, 28)))
port map( I_CLK => I_CLK, --baud_clk,--
I_CLR => I_CLR,
I_RD => L_RD_UART,
I_WE => L_WE_UART,
I_TX_DATA => I_DIN(7 downto 0),
I_RX => I_RX,
Q_TX => Q_TX,
Q_RX_DATA => U_RX_DATA,
Q_RX_READY => U_RX_READY,
Q_TX_BUSY => U_TX_BUSY);
LeftMotor:HB5RefComp port map (
ck => I_CLK,
bitDirIn => s_LeftDirIn,
vecDfIn => s_vecDfIn_Gauche,
bitEnOut => LeftEnOut,
bitDirOut =>LeftDirOut
);
RightMotor:HB5RefComp port map (
ck => I_CLK,
bitDirIn => s_RightDirIn,
vecDfIn => s_vecDfIn_Droite,
bitEnOut => RightEnOut,
bitDirOut =>RightDirOut
);
--> added 2011/10/19
baud_process: process(I_CLK) begin
if rising_edge(I_CLK) then
baud_clk <= not baud_clk;
end if;
end process;
--<
-- IO read process
--
iord: process(I_ADR_IO, I_PINB,
U_RX_DATA, U_RX_READY, L_RX_INT_ENABLED,
U_TX_BUSY, L_TX_INT_ENABLED)
begin
-- addresses for mega8 device (use iom8.h or #define __AVR_ATmega8__).
--
case I_ADR_IO is
when X"2A" => Q_DOUT <= -- UCSRB:
L_RX_INT_ENABLED -- Rx complete int enabled.
& L_TX_INT_ENABLED -- Tx complete int enabled.
& L_TX_INT_ENABLED -- Tx empty int enabled.
& '1' -- Rx enabled
& '1' -- Tx enabled
& '0' -- 8 bits/char
& '0' -- Rx bit 8
& '0'; -- Tx bit 8
when X"2B" => Q_DOUT <= -- UCSRA:
U_RX_READY -- Rx complete
& not U_TX_BUSY -- Tx complete
& not U_TX_BUSY -- Tx ready
& '0' -- frame error
& '0' -- data overrun
& '0' -- parity error
& '0' -- double dpeed
& '0'; -- multiproc mode
when X"2C" => Q_DOUT <= U_RX_DATA; -- UDR
when X"40" => Q_DOUT <= -- UCSRC
'1' -- URSEL
& '0' -- asynchronous
& "00" -- no parity
& '1' -- two stop bits
& "11" -- 8 bits/char
& '0'; -- rising clock edge
when X"36" => Q_DOUT <= I_PINB; -- PINB
when X"52" => Q_DOUT <= pseudotimer(19 downto 12); --TCNT0
when others => Q_DOUT <= X"AA";
end case;
end process;
-- IO write process
--
iowr: process(I_CLK)
begin
if (rising_edge(I_CLK)) then
if (I_CLR = '1') then
L_RX_INT_ENABLED <= '0';
L_TX_INT_ENABLED <= '0';
elsif (I_WE_IO = '1') then
case I_ADR_IO is
when X"38" => -- PORTB
s_vecDfIn_Gauche <= I_DIN;
--L_LEDS <= not L_LEDS;
when X"35" => -- PORTC
s_vecDfIn_Droite <= I_DIN;
when X"37" => --DDRB
s_RightDirIn <= I_DIN(0);
s_LeftDirIn <= I_DIN(1);
when X"32" => -- PORTD
Q_PORTD <= I_DIN;
when X"2A" => -- UCSRB
L_RX_INT_ENABLED <= I_DIN(7);
L_TX_INT_ENABLED <= I_DIN(6);
when X"2B" => -- UCSRA: handled by uart
when X"2C" => -- UDR: handled by uart
when X"40" => -- UCSRC/UBRRH: (ignored)
when X"59" => -- TIMSK
L_TOIE0 <= I_DIN(0);
when others =>
end case;
end if;
end if;
end process;
-- L_RESET_TOV0 <= '1' when (I_ADR_IO = X"58" and I_DIN(0)='1') else --TIFR
-- '1' when (I_ADR_IO = X"52" and I_WE_IO = '1') else --TCNT0
-- '0';
-- interrupt process
--
ioint: process(I_CLK)
begin
if (rising_edge(I_CLK)) then
if (I_CLR = '1') then
L_INTVEC <= "000000";
else
case L_INTVEC is
-- vector 9
when "101001" =>
if (L_TOIE0 and L_TOV0) = '0' then
L_INTVEC <= "000000";
end if;
-- vector 11 ??
when "101011" => -- vector 11 interrupt pending.
if (L_RX_INT_ENABLED and U_RX_READY) = '0' then
L_INTVEC <= "000000";
end if;
-- vector 12 ??
when "101100" => -- vector 12 interrupt pending.
if (L_TX_INT_ENABLED and not U_TX_BUSY) = '0' then
L_INTVEC <= "000000";
end if;
when others =>
-- no interrupt is pending.
-- We accept a new interrupt.
--
if (L_RX_INT_ENABLED and U_RX_READY) = '1' then
L_INTVEC <= "101011"; -- _VECTOR(11)
elsif (L_TX_INT_ENABLED and not U_TX_BUSY) = '1' then
L_INTVEC <= "101100"; -- _VECTOR(12)
elsif (L_TOIE0 and L_TOV0) = '1' then
L_INTVEC <= "101001"; -- _VECTOR(9)
else
L_INTVEC <= "000000"; -- no interrupt
end if;
end case;
end if;
end if;
end process;
L_WE_UART <= I_WE_IO when (I_ADR_IO = X"2C") else '0'; -- write UART UDR
L_RD_UART <= I_RD_IO when (I_ADR_IO = X"2C") else '0'; -- read UART UDR
Q_INTVEC <= L_INTVEC;
timer0: process(I_CLK) begin
if (rising_edge(I_CLK)) then
if (I_ADR_IO = X"52" and I_WE_IO = '1') then
pseudotimer(19 downto 12) <= I_DIN;
L_TOV0 <= '0';
else
pseudotimer <= pseudotimer + 1;
if pseudotimer = x"FFFFF" then
L_TOV0 <= '1';
end if;
end if;
end if;
end process;
end Behavioral;
Programmation des pmodHB5 avec l'ATMega16
[modifier | modifier le wikicode]Voici un exemple de programme qui utilise cette interface.
int main(int argc, char * argv[])
{
while(1){
// choix de la direction : en avant : voir code iotimer.vhd
DDRB = 0x00;
// moteur droit en avant
PORTC = 0x55;
// moteur gauche en avant
PORTB = 0x55;
_delay_ms(1000);
// on arrête
PORTC = 0x00;
PORTB = 0x00;
// changement de direction pour un moteur :
DDRB = 0x01;
PORTC = 0x55;
PORTB = 0x55;
_delay_ms(1000);
// on arrête
PORTC = 0x00;
PORTB = 0x00;
// changement de direction pour un moteur :
DDRB = 0x02;
PORTC = 0x55;
PORTB = 0x55;
_delay_ms(1000);
// on arrête
PORTC = 0x00;
PORTB = 0x00;
// changement de direction pour deux moteurs :
DDRB = 0x03;
PORTC = 0x55;
PORTB = 0x55;
_delay_ms(1000);
// on arrête
PORTC = 0x00;
PORTB = 0x00;
}
return 0;
}
Que fait ce programme ?
Arrivé à ce point, il serait probablement très utile de réaliser des sous-programmes permettant d'aller en ligne droite pendant un certain temps et de tourner de 90° ou mieux encore de tourner d'un angle fourni en paramètre. Pour le moment nous laissons le soin de réaliser ce travail au lecteur. Mais d'autres sous-programmes (intéressants ?) sont présentés un peu plus loin...
Maintenant que nous sommes capables de faire bouger notre robot, le problème de savoir où l’on est à tout moment se pose sérieusement.
Se repérer dans l'espace
[modifier | modifier le wikicode]Dans cette section, nous allons examiner comment se repérer dans l'espace à l'aide de compteurs (un par roues, donc deux compteurs). Ces compteurs devront mémoriser la distance de déplacement effectuée par chacune des roues. Mais expliquons d’abord comment ceci va être réalisé en pratique.
La commande des moteurs par les modules PModHB5 nous a pris seulement deux fils plus l'alimentation comme on peut facilement le voir dans le schéma qui explicite l'interface de cette commande. Pourtant une autre information est disponible, comme déjà évoqué. Il s'agit de codeurs incrémentaux à effet Hall. Nous désirons les mettre en œuvre dans cette section.
Nous allons d’abord tenter de répondre à la question de la difficulté du problème.
Pourquoi le problème est difficile
[modifier | modifier le wikicode]Avant de se poser des questions il faut essayer de bien comprendre ce que veut dire connaître exactement l'état du robot. Celui-ci est donné par trois paramètres même si l’on est en deux dimensions : . x et y sont naturellement le repérage de la position sur les deux axes x et y et est l'orientation du robot par rapport à l'axe des x. Regardez la figure ci-contre pour vous convaincre de la pertinence de ces trois données.

Imaginons que vous ayez fait déplacer votre robot pendant un temps T quelconque. Vous relevez les compteurs de chacune des roues et convertissez en centimètres parcourus et trouvez deux nombres A et B. Est-il possible d’en déduire la nouvelle position (et orientation) sachant que l’on connaît exactement la position de départ ? Autre manière de poser le problème : je connais parfaitement ainsi que la distance parcourue par chacune des roues est-il possible d’en déduire ? Malheureusement la réponse à cette question est partiellement négative ! Il n'est possible que d’avoir mais pas le reste.
Tout cela est résumé simplement par la figure ci-contre. Dans cette figure, les deux longueurs (en pointillés) rouges sont identiques (et égales à A) et les deux longueurs (en pointillés) bleues sont elles aussi identiques (et égales à B) et pourtant on obtient deux points d'arrivées différents. Seule l'orientation finale est identique. Cela montre qu'aucune formule ne permet le calcul de xf à partir de xd, A et B.
Doit-on abandonner l’idée de trouver le point final à partir du point de départ ? Non, heureusement. En fait le problème présenté devient possible si les deux distances parcourues A et B deviennent différentielles (c'est-à-dire toutes petites). C'est quand même une mauvaise nouvelle malgré tout. Cela veut dire qu’il nous faut mémoriser les distances parcourues par chacune des roues à des instants précis et assez fréquemment pour se rapprocher de l’idée mathématique de différentielle. Nous essaierons dans un premier temps dix mesures par seconde. Nous utiliserons des compteurs sur 8 bits en complément à deux... et dix fois par seconde nous mémoriserons donc les valeurs des deux compteurs (droit et gauche) dans une mémoire.
Le problème de la gestion de la mémoire sera examiné plus loin. Pour ce qui est de la taille, une mémoire de 2 ko permet de mémoriser 1024 valeurs de 16 bits correspondant aux deux compteurs (roue droite et roue gauche). 1024 valeurs correspondent à 100 s (car 10 enregistrements par seconde) soit une minute et demi. Pas grandiose ! mais suffisant pour examiner quelques problèmes intéressants avec des étudiants.
Une autre solution qui sera examinée dans un futur projet (voir en fin de chapitre) sera de calculer au fur et à mesure les valeurs de x et y.
Pour l'instant nous allons présenter le module fourni par Digilent qui est destiné à mesurer la vitesse angulaire d'une roue.
Présentation du module de mesure de vitesse de Digilent
[modifier | modifier le wikicode]Mesurer la vitesse à l'aide des modules PModHB5 est une opération qui est proposée par Digilent à l'aide d'un module VHDL appelé "HallTradSpeedDecoder" dont voici l'entité :
entity HallTradSpeedDecoder is
Port ( ck : in STD_LOGIC;
pinSA : in STD_LOGIC;
pinSB : in STD_LOGIC;
SpeedOut : out STD_LOGIC_VECTOR (12 downto 0)); -- output for speed and direction
end HallTradSpeedDecoder;
On voit apparaître une horloge "ck" qui est supposée être à 100 MHz (puisque ce module est destiné à la Nexys3), deux entrées "pinSA" et "pinSB" supposées être en provenance du codeur incrémental et une vitesse de sortie sur 12 bits. La vitesse est directement exprimée en tours par minute et codée en BCD.
- une roue passive (non motrice) qui roule et ne glisse pas couplée à un codeur incrémental. Ici il en faudrait deux pour avoir une information de direction.
- utilisation d'un accéléromètre.
Nous allons essayer de modifier ce module pour tenter de réaliser un périphérique capable de mémoriser les positions.
Bien comprendre la base de temps
[modifier | modifier le wikicode]Dans le module original apparaît une base de temps. Nous allons la changer.
La base de temps est (100 000 000 x 60) /(12 x 19) = 26315789. Cette valeur est entièrement liée au fait que l’on a 12 fronts sur les deux entrées pour un tour de l'axe moteur associé à un facteur de réduction de 19. Ainsi, si l’on compte pendant ce temps on aura directement la vitesse en tour par minute.
On veut une mise à jour d'une dizaine de fois par seconde.
Supprimer la gestion BCD
[modifier | modifier le wikicode]La gestion du BCD était utile dans le module original dans la mesure où la vitesse devait être affichée sur des afficheurs sept segments. En ce qui nous concerne l'information sera donnée au processeur qui pourra faire toutes les conversions imaginables par programme.
Cette suppression de la gestion du BCD est simple à réaliser : il suffit de repérer les commentaires d'incrémentation et de décrémentations et de les réaliser en binaire. C'est beaucoup plus simple et l’on peut espérer n'avoir qu'un octet pour mémoriser ce nombre (au lieu de trois).
Utiliser le timer de l'AVR embarqué comme base de temps
[modifier | modifier le wikicode]La base de temps du module original est réalisée par :
--.................. code supprimé pour simplification
constant DivFactor: integer := 26315789; -- division factor
--.................. code supprimé pour simplification
-- cntSpeed Enable signal
cntSpeedEn <= '1' when TimeBaseCounter = 0 else '0';
-------------------------------
-- System Clock Devider Process
-------------------------------
TimeBaseCounterProc: process(ck)
begin
if ck'event and ck = '1' then
if TimeBaseCounter /= DivFactor - 1 then
TimeBaseCounter <= TimeBaseCounter + 1;
else TimeBaseCounter <= 0;
end if;
end if;
end process;
--.................. code supprimé pour simplification
Nous allons supprimer ce timer pour le remplacer par celui de l'AVR décrit dans un chapitre précédent. L'intérêt est que celui de l'AVR est capable de déclencher une interruption. Ainsi à chaque interruption les deux compteurs du côté droit et du côté gauche seront sauvés en mémoire par l'AVR.
Le pseudo-timer de l'AVR ne présente pas de préscalaire. Il est sur 20 bits et déborde avec une fréquence de 95 Hz sur la Nexys3 qui sera d'environ 43 Hz sur la Basys2. Il faut donc la diviser par 4 pour avoir environ 10 mises à jour par seconde. Cela revient tout simplement à ajouter deux bits au timer.
Décodeur incrémental simple
[modifier | modifier le wikicode]Nous avons fini par abandonner l’utilisation du code de Digilent pour utiliser une variation beaucoup plus simple. Il s'agit d'un code proposé par Ken Chapman (le concepteur du picoBlaze) pour décoder un bouton incrémental. Voici donc ce code modifié pour nos besoins :
-- Ken Chapman - Xilinx Ltd - November 2005
-- Revised 20th February 2006
-- Adapted 5th May 2014 (Serge Moutou)
------------------------------------------------------------------------------------
--
-- NOTICE:
--
-- Copyright Xilinx, Inc. 2006. This code may be contain portions patented by other
-- third parties. By providing this core as one possible implementation of a standard,
-- Xilinx is making no representation that the provided implementation of this standard
-- is free from any claims of infringement by any third party. Xilinx expressly
-- disclaims any warranty with respect to the adequacy of the implementation, including
-- but not limited to any warranty or representation that the implementation is free
-- from claims of any third party. Furthermore, Xilinx is providing this core as a
-- courtesy to you and suggests that you contact all third parties to obtain the
-- necessary rights to use this implementation.
--
------------------------------------------------------------------------------------
------------------------------------------------------------------------------------
--
-- Library declarations
--
-- Standard IEEE libraries
--
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
--
------------------------------------------------------------------------------------
--
--
entity incrementalDecodeur is
Port ( position : out std_logic_vector(7 downto 0);
rotary_a : in std_logic;
rotary_b : in std_logic;
reset : in std_logic;
clk : in std_logic);
end incrementalDecodeur;
--
------------------------------------------------------------------------------------
--
-- Start of test architecture
--
architecture Behavioral of incrementalDecodeur is
--
------------------------------------------------------------------------------------
--
-- Signals used to interface to rotary encoder
--
signal rotary_a_in : std_logic;
signal rotary_b_in : std_logic;
signal rotary_in : std_logic_vector(1 downto 0);
signal rotary_q1 : std_logic;
signal rotary_q2 : std_logic;
signal delay_rotary_q1 : std_logic;
signal rotary_event : std_logic;
signal rotary_left : std_logic;
--
-- Signals used to drive LEDs
--
signal position_counter : std_logic_vector(7 downto 0):= "00010000"; --initial value puts one LED on near the middle.
--
---------------------------------------------------------------------------------
--
-- Start of circuit description
--
begin
--
-------------------------------------------------------------------------------
-- Interface to rotary encoder.
-- Detection of movement and direction.
-------------------------------------------------------------------------------
--
-- The rotary switch contacts are filtered using their offset (one-hot) style to
-- clean them. Circuit concept by Peter Alfke.
-- Note that the clock rate is fast compared with the switch rate.
rotary_filter: process(clk)
begin
if clk'event and clk='1' then
--Synchronise inputs to clock domain using flip-flops in input/output blocks.
rotary_a_in <= rotary_a;
rotary_b_in <= rotary_b;
--concatinate rotary input signals to form vector for case construct.
rotary_in <= rotary_b_in & rotary_a_in;
case rotary_in is
when "00" => rotary_q1 <= '0';
rotary_q2 <= rotary_q2;
when "01" => rotary_q1 <= rotary_q1;
rotary_q2 <= '0';
when "10" => rotary_q1 <= rotary_q1;
rotary_q2 <= '1';
when "11" => rotary_q1 <= '1';
rotary_q2 <= rotary_q2;
when others => rotary_q1 <= rotary_q1;
rotary_q2 <= rotary_q2;
end case;
end if;
end process rotary_filter;
--
-- The rising edges of 'rotary_q1' indicate that a rotation has occurred and the
-- state of 'rotary_q2' at that time will indicate the direction.
--
direction: process(clk)
begin
if clk'event and clk='1' then
delay_rotary_q1 <= rotary_q1;
if rotary_q1='1' and delay_rotary_q1='0' then
rotary_event <= '1';
rotary_left <= rotary_q2;
else
rotary_event <= '0';
rotary_left <= rotary_left;
end if;
end if;
end process direction;
--
--
----------------------------------------------------------------------
-- Position control.
----------------------------------------------------------------------
--
position_control: process(clk)
begin
if clk'event and clk='1' then
if reset = '1' then
position_counter <= (others=>'0');
elsif rotary_event='1' then
if rotary_left='1' then
position_counter <= position_counter + 1; --increment position
else
position_counter <= position_counter - 1; --decrement position
end if;
end if;
--Ouput
position <= position_counter;
end if;
end process position_control;
--
end Behavioral;
--------------------------------------------------------------------------
--
-- END OF FILE left_right_leds.vhd
--
--------------------------------------------------------------------------
Ce code fonctionne correctement à priori même sans gestion des rebonds. C'est une particularité des signaux incrémentaux de ne pas nécessiter toujours d'une gestion d'anti-rebonds. Cela reste vrai pour un affichage (qui est une fonction lente) mais si l’on échantillonne le compteur pendant des rebonds, il y aura une erreur qui finira par s'accumuler. Tout ceci nécessite donc une vérification minutieuse.
Interfacer ce module à l'ATMega16
[modifier | modifier le wikicode]Comme d'habitude nous présentons l'interface avec l'ATMega16 sous forme de schéma :

Cette figure présente une version provisoire de l'interface. Nous avons connecté l'entrée "reset" de notre module à l'entrée "I_CLR" du module io.vhd (même si ce n’est pas visible sur le dessin) et ceci ne peut rester comme cela. La raison ? Rappelons qu’il nous faut :
- mémoriser les valeurs de la sortie "position" dans une mémoire au fur et à mesure que le temps s'écoule. Cela signifie en particulier qu'une fois la valeur lue, il faut remettre à 0 le compteur position par programme. Ceci est impossible si l'AVR n'a pas accès au "reset".
- mémoriser peut-être les valeurs dans un grand compteur (au moins 16 bits) ce qui éviterait de perdre des données lors des lectures intermédiaires. En effet un reset en même temps qu'une incrémentation ou décrémentation fera perdre une donnée
Tout ceci est susceptible d'encore évoluer... mais sera certainement stable lorsque nous présenterons le cahier des charges du projet 2014
Réaliser un programme pour les tests
[modifier | modifier le wikicode]Pour tester ce module sur une seule roue, nous avons réalisé le programme suivant :
#include <avr/io.h>
#include <avr/interrupt.h>
#undef F_CPU
#define F_CPU 25000000UL
#include "util/delay.h"
void readFIFOandAskForNextData(unsigned char nunchukDATA[]);
void enAvant(unsigned char droite, unsigned char gauche);
void enArriere(unsigned char droite, unsigned char gauche);
void tourneDroite(unsigned char gauche);
void tourneGauche(unsigned char gauche);
int main(int argc, char * argv[])
{
unsigned char nunchukDATA[6],vitesseDroite,vitesseGauche,avant,leftPosition;
while(1){
readFIFOandAskForNextData(nunchukDATA);
if (!(nunchukDATA[5] & 1)) {// Z button
if (nunchukDATA[1]>0xA0) {vitesseDroite=vitesseGauche=nunchukDATA[1]-0x80; avant=1;}
else if (nunchukDATA[1]<0x60) {vitesseDroite=vitesseGauche=0x80-nunchukDATA[1];avant=0; }
else {vitesseGauche=vitesseDroite=0x00;}
if (nunchukDATA[0]>0xA0) vitesseDroite = vitesseDroite -nunchukDATA[0]+0x80;
else if (nunchukDATA[0]<0x60) vitesseGauche = vitesseGauche + nunchukDATA[0]-0x80;
if (avant) enAvant(vitesseGauche,vitesseDroite); else enArriere(vitesseGauche,vitesseDroite);
} else if (!(nunchukDATA[5] & 2)) {// C button
if (nunchukDATA[2]>0xB0) tourneDroite(0x55);
else if (nunchukDATA[2]<0x55) tourneGauche(0x55);
else if (nunchukDATA[3]>0x90) enAvant(0x55,0x55);
else if (nunchukDATA[3]<0x80) enArriere(0x55,0x55);
else enAvant(0x00,0x00);
}
else enAvant(0x00,0x00);
//PORTA = vitesseGauche;
leftPosition = PIND;
PORTD = leftPosition;
_delay_ms(100);
}
return 0;
}
La compréhension de ce code nécessite de lire la section sur la manette Nunchuk un peu plus loin.
L'idée de ce code est d’utiliser la manette Nunchuk pour déplacer le robot et sans arrêt lire la valeur d'un compteur de position sur 8 bits et de l'afficher directement sur des leds.
Réaliser un programme de traitement de données pour trouver la trajectoire
[modifier | modifier le wikicode]Nous allons essayer de retrouver la trajectoire de notre robot mobile à partir des données fournies par l'AVR. L’idée générale est que l'AVR mémorise les valeurs de ses compteurs régulièrement et qu'une fois terminé ces valeur sont transmise à un PC en vue d'un calcul. Le calcul se faisant sur un PC on peut utiliser les librairies standard du C, en particulier les cosinus et sinus.
Autre moyen de se repérer : le module infrarouge PmodLS1
[modifier | modifier le wikicode]Le module infrarouge permet de se repérer mais pas en absolu. Il permet en effet de suivre une ligne, par exemple mais cela nécessite naturellement que cette ligne soit dessinée. Une autre possibilité est de réaliser un quadrillage et espérer repérer les lignes horizontales et verticales quand on les franchit. Mais réaliser un programme qui réalise cela doit être assez complexe : Nous n'avons jamais rencontré d'exemple sur Internet !
Avant de se lancer dans une programmation compliquée, revenons sur terre pour examiner le PmodLS1. Ce module est branché directement sur le connecteur JA de la Basys2. Mais il demande à être calibré à l'aide d'un réglage de potentiomètre. Le réglage peut se faire facilement à l'aide d'un programme tout simple :
int main(int argc, char * argv[])
{
while(1){
PORTD=PINB;
}
return 0;
}
si l’on a pris soin de sortir le PORTD sur les LEDs (ceci se fait dans l'ucf) et de mettre les entrées du connecteur JA sur PINB. Voici l'extrait de l'ucf correspondant :
####### Only for Basys2 ######## # # Pin assignment for LEDs NET "Q_PORTD<7>" LOC = "G1" ; # Bank = 3, Signal name = LD7 NET "Q_PORTD<6>" LOC = "P4" ; # Bank = 2, Signal name = LD6 NET "Q_PORTD<5>" LOC = "N4" ; # Bank = 2, Signal name = LD5 NET "Q_PORTD<4>" LOC = "N5" ; # Bank = 2, Signal name = LD4 NET "Q_PORTD<3>" LOC = "P6" ; # Bank = 2, Signal name = LD3 NET "Q_PORTD<2>" LOC = "P7" ; # Bank = 3, Signal name = LD2 NET "Q_PORTD<1>" LOC = "M11" ; # Bank = 2, Signal name = LD1 NET "Q_PORTD<0>" LOC = "M5" ; # Bank = 2, Signal name = LD0 # Loop Back only tested signals #photo transistor NET "I_PINB<0>" LOC = "B2" | DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = JA1 NET "I_PINB<1>" LOC = "A3" | DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = JA2 NET "I_PINB<2>" LOC = "J3" | DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = JA3 NET "I_PINB<3>" LOC = "B5" | DRIVE = 2 | PULLUP ; # Bank = 1, Signal name = JA4
Si vous prenez une surface blanche sur laquelle vous avez collé du scotch noir vous devez voir les différentes leds s'allumer et s'éteindre en fonction de leur positions par rapport à la ligne noir.
Pour information nous avons eu l’idée de remplacer le module pmodLS1 par un Arduino micro en 3,3 V pour utiliser ses convertisseurs analogiques numériques et ainsi pouvoir régler les seuils (blanc/noir) de manière indépendante. Mais les étudiants chargés de ce projet en 2014/2015 n'ont pas abouti. Il a été présenté dans ce livre comme un projet autour d'un MSP430 mais nous avons changé au dernier moment de processeur pour des questions de réalisation de circuit imprimé. Mais sans carte digne de remplacer les pmodLS1, nous abandonnons l'infra-rouge pour le moment.
Le gros problème du module PModLS1 est qu'il n'y qu'un potentiomètre pour régler le seuil de chacun des capteurs infrarouges. Un collègue s'est lancé dans une réalisation avec un potentiomètre par capteur. Espérons qu'elle aboutira bientôt.
Cet abandon nous a amené à étudier un autre domaine que nous allons présenter maintenant.
Contrôle du robot par une manette Nunchuk
[modifier | modifier le wikicode]Nous avons déjà utilisé la manette Nunchuk pour contrôler un jeu vidéo dans un autre chapitre. Nous avons donc décidé d’utiliser le travail déjà fait pour notre robot.
Visualisez Using an Arduino and Wii Nunchuck to Control a Robot (Youtube). Vous y verrez un exemple de ce que l’on cherche à faire mais avec un autre robot.
Interfacer la manette Nunchuk
[modifier | modifier le wikicode]Pour ceux qui ont lu ce livre en entier cette section paraîtra comme du déjà vue. C'est normal on reprend ici le travail fait dans ce domaine pour le pacman.
Un module capable de gérer TOUTES les données de la Nunchuk est disponible dans un autre chapitre. C'est lui que vous allez utiliser et interfacer dans "io2.vhd". Vous utiliserez deux registres réservés à l’i2c dans l'ATMega16 :
- TWDR en adresse 0x23 pour les données
- TWCR en adresse 0x56 pour les commandes et états commande TWEN est en bit b2 (écriture) et l'état TWINT = data présent en bit b7 (lecture)
Voici un condensé sous forme de schéma de ce qu’il y a à faire :

Même si cette figure ne représente pas tous les registres et PORTs que nous utilisons dans le projet pacman complet elle nous montre l'essentiel. L'ensemble du travail consiste donc à câbler et ajouter des lignes dans les deux "case" des deux process de lecture et d'écriture. Ce qui est peut être un peu délicat à comprendre est la réalisation du signal "s_readFIFO" tout en haut de la figure. Ce signal est là pour vider le FIFO au fur et à mesure de ses lectures par le processeur. Il sera relié à l'entrée readFIFO du module NunchukTop.
Réaliser un programme pour utiliser la manette Nunchuk
[modifier | modifier le wikicode]Nous allons vous donner deux sous-programmes pour vous aider à démarrer votre projet.
Lecture du FIFO et demande de données
[modifier | modifier le wikicode]Le sous-programme présenté ci-dessous est appelé avec un nom très long qui, nous l'espérons, sera assez évocateur. Il fait deux choses :
- lecture du FIFO qui a été rempli par la manette
- départ d'une nouvelle demande d'information à la Nunchuk
Il devra être inséré dans un boucle (éventuellement infinie) pas trop rapide pour que la fois suiivante le FIFO soit correctement rempli.
Voici le code source correspondant :
//******************************************************************************************************************************
// function readFIFOandAskForNextData()
// purpose: read FIFO and put data in array and start the request of next data
// arguments: array of unsigned char to store FIFO data
//
// return:
// note: you cannot understand if you never read the corresponding VHDL code (in io2.vhd)
//******************************************************************************************************************************
void readFIFOandAskForNextData(unsigned char nunchukDATA[]){
unsigned char i;
while(!(TWCR & (1<<TWINT))); // attente données dans le FIFO
for (i=0;i<6;i++) //lecture du Nunchuk du FIFO dans tableau
nunchukDATA[i]=TWDR;
// on demande une nouvelle lecture par le pérphérique : toutes données seront pour la prochaine fois
TWCR |= (1<<TWEN); //depart
TWCR &= ~(1<<TWEN);// arret pour attendre la prochaine fois : voir machine d'états pour comprendre
}
Attente d'un départ
[modifier | modifier le wikicode]Il est naturellement possible de bloquer le robot en attendant un événement sur la Nunchuk.
Voici comment on peut bloquer tout en attendant un appui sur le bouton Z :
//******************************************************************************************************************************
// function waitStart()
// purpose: wait Z button start : Doesn't work properrly at the moment !!!
// arguments:
//
// return:
// note: you cannot understand if you never read the corresponding VHDL code (in io2.vhd)
//******************************************************************************************************************************
void waitStart(){
unsigned char data[6],loop=1;
while(loop) {
readFIFOandAskForNextData(data);
if ((data[5] & 0x01)==0x00) loop=0;
_delay_ms(10); // car data_present mal géré et on ne prend pas de risque
}
On rappelle pour comprendre le code, que les deux boutons sont peésents dans le 6° octet retourné par la Nunchuk (donc la case 5 de notre tableau) que les deux boutons sont les deux poids faibles et qu’ils sont à 1 lorsqu’ils ne sont pas appuyés.
Programme Joystick
[modifier | modifier le wikicode]Calibrage Joystick analogique :
- gauche à fond : 0x20
- droite à fond : 0xE1
- repos 7F
- avant à fond : 0xE0
- arrière à fond : 0x25
- repos 0x80
//****************** Version à partir de la version étudiant (testee OK) ***************
//****************** Mars 2014
//#include "stdint.h"
#include "avr/io.h"
//#include "avr/pgmspace.h"
#undef F_CPU
#define F_CPU 25000000UL
#include "util/delay.h"
#define DATAPRESENT 2
#define PORTA _SFR_IO8(0x1B)
void readFIFOandAskForNextData(unsigned char nunchukDATA[]);
void enAvant(unsigned char droite, unsigned char gauche);
void enArriere(unsigned char droite, unsigned char gauche);
void tourneDroite(unsigned char gauche);
void tourneGauche(unsigned char gauche);
int main(int argc, char * argv[])
{
unsigned char nunchukDATA[6],vitesseDroite,vitesseGauche,avant;
while(1){
readFIFOandAskForNextData(nunchukDATA);
if (!(nunchukDATA[5] & 1)) {// Z button
if (nunchukDATA[1]>0xA0) {vitesseDroite=vitesseGauche=nunchukDATA[1]-0x80; avant=1;}
else if (nunchukDATA[1]<0x60) {vitesseDroite=vitesseGauche=0x80-nunchukDATA[1];avant=0; }
else {vitesseGauche=vitesseDroite=0x00;}
if (nunchukDATA[0]>0xA0) vitesseDroite = vitesseDroite -nunchukDATA[0]+0x80;
else if (nunchukDATA[0]<0x60) vitesseGauche = vitesseGauche + nunchukDATA[0]-0x80;
if (avant) enAvant(vitesseGauche,vitesseDroite); else enArriere(vitesseGauche,vitesseDroite);
} else if (!(nunchukDATA[5] & 2)) {// C button
if (nunchukDATA[2]>0xB0) tourneDroite(0x55);
else if (nunchukDATA[2]<0x55) tourneGauche(0x55);
else if (nunchukDATA[3]>0x90) enAvant(0x55,0x55);
else if (nunchukDATA[3]<0x80) enArriere(0x55,0x55);
else enAvant(0x00,0x00);
}
else enAvant(0x00,0x00);
_delay_ms(100);
}
return 0;
}
//******************************************************************************************************************************
// function readFIFOandAskForNextData()
// purpose: read FIFO and put data in array and start the request of next data
// arguments: array of unsigned char to store FIFO data
//
// return: nothing
// note: you cannot understand if you never read the corresponding VHDL code (in iotimer.vhd)
//******************************************************************************************************************************
void readFIFOandAskForNextData(unsigned char nunchukDATA[]){
unsigned char i;
while(!(TWCR & (1<<TWINT))); // attente données dans le FIFO
for (i=0;i<6;i++) //lecture du Nunchuk du FIFO dans tableau
nunchukDATA[i]=TWDR;
// on demande une nouvelle lecture par le pérphérique : toutes données seront pour la prochaine fois
TWCR |= (1<<TWEN); //depart
TWCR &= ~(1<<TWEN);// arret pour attendre la prochaine fois : voir machine d'états pour comprendre
}
//******************************************************************************************************************************
// function enAvant()
// purpose: goAhead
// arguments: left and right speed
//
// return: nothing
// note: you cannot understand if you never read the corresponding VHDL code (in iotimer.vhd)
//******************************************************************************************************************************
void enAvant(unsigned char droite, unsigned char gauche) {
// par sécurité toute fonction commence par l'arrêt des moteurs
PORTC = 0x00;
PORTB = 0x00;
_delay_ms(10);
// les deux moteurs en marche avant
// choix de la direction : en avant : voir code iotimer.vhd
DDRB = 0x02;
PORTC = droite;
PORTB = gauche;
}
//******************************************************************************************************************************
// function enArriere()
// purpose: goBack
// arguments: left and right speed
//
// return: nothing
// note: you cannot understand if you never read the corresponding VHDL code (in iotimer.vhd)
//******************************************************************************************************************************
void enArriere(unsigned char droite, unsigned char gauche) {
// par sécurité toute fonction commence par l'arrêt des moteurs
PORTC = 0x00;
PORTB = 0x00;
_delay_ms(10);
// les deux moteurs en marche arriere
// choix de la direction : en avant : voir code iotimer.vhd
DDRB = 0x01;
PORTC = droite;
PORTB = gauche;
}
//******************************************************************************************************************************
// function TourneDroite()
// purpose: turn on the right
// arguments: left speed (right speed is zero)
//
// return: nothing
// note: you cannot understand if you never read the corresponding VHDL code (in iotimer.vhd)
//******************************************************************************************************************************
void tourneDroite(unsigned char gauche) {
// par sécurité toute fonction commence par l'arrêt des moteurs
PORTC = 0x00;
PORTB = 0x00;
_delay_ms(10);
// les deux moteurs en marche avant
// choix de la direction : en avant : voir code iotimer.vhd
DDRB = 0x02;
PORTB = gauche;
}
//******************************************************************************************************************************
// function tourneGauche()
// purpose: turn on the left
// arguments: right speed (left speed is zero)
//
// return: nothing
// note: you cannot understand if you never read the corresponding VHDL code (in iotimer.vhd)
//******************************************************************************************************************************
void tourneGauche(unsigned char droite) {
// par sécurité toute fonction commence par l'arrêt des moteurs
PORTC = 0x00;
PORTB = 0x00;
_delay_ms(10);
// les deux moteurs en marche avant
// choix de la direction : en avant : voir code iotimer.vhd
DDRB = 0x02;
PORTC = droite;
}
Mise en pratique du repérage dans l'espace avec des codeurs de roues
[modifier | modifier le wikicode]Nous avons présenté de manière théorique le repérage dans l'espace à l'aide de codeurs de roues. Nous allons tenter maintenant de mettre cela en pratique avec notre robot.
Calcul des coordonnées x et y du robot
[modifier | modifier le wikicode]L'objectif est de coupler le module de repérage de position au coprocesseur CORDIC. L'intérêt de ce projet est d’éviter la mémorisation continue des compteurs de roue en calculant au fur et à mesure l'état . La formule de calcul faisant intervenir un cosinus et un sinus, nous allons coupler les compteurs avec le coprocesseur CORDIC du précédent chapitre.
Ajouter un décodeur incrémental
[modifier | modifier le wikicode]Nous allons vous donner une version corrigée du projet de l'année passée. Cette version possède un seul décodeur incrémental pour la roue gauche. On vous demande à partir de ce code :
- de gérer le reset de manière automatique lors de la lecture de la position
- d'ajouter un décodeur incrémental pour l'autre roue en le gérant de la même manière. Par exemple la position peut être reliée à PINC (adresse 0x33)
- ajouter la gestion des afficheurs sept segments (les 4) pour faciliter les tests.
Un programme d'affichage automatique des valeurs de position gauche et droite sera alors, par exemple :
#include <avr/io.h>
#include <avr/interrupt.h>
#undef F_CPU
#define F_CPU 25000000UL
#include "util/delay.h"
void readFIFOandAskForNextData(unsigned char nunchukDATA[]);
void enAvant(unsigned char droite, unsigned char gauche);
void enArriere(unsigned char droite, unsigned char gauche);
void tourneDroite(unsigned char gauche);
void tourneGauche(unsigned char gauche);
const unsigned char digit7segs[16]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E};
volatile unsigned char s7segs,selANi=0xFE;
volatile unsigned int toPrint; // l'interruption se servira de cela pour affichier
ISR(TIMER0_OVF_vect) // ISR(_VECTOR(9))
{
//selANi <<= 1;
//if (selANi <= 0xEF) selANi = 0xFE;
PIND = selANi;
switch (selANi) {
case 0xFE : DDRD = digit7segs[toPrint&0x000F];selANi=0xFD;break;
case 0xFD : DDRD = digit7segs[(toPrint>>4)&0x000F];selANi=0xFB;break;
case 0xFB : DDRD = digit7segs[(toPrint>>8)&0x000F];selANi=0xF7;break;
case 0xF7 : DDRD = digit7segs[(toPrint>>12)&0x000F];selANi=0xFE;break;
default : DDRD = 0xBF;selANi=0xFE;
}
TCNT0 = 192; // remet TOV0 à 0 !!!!
}
int main(int argc, char * argv[])
{
unsigned char nunchukDATA[6],vitesseDroite,vitesseGauche,avant,leftPosition,rightPosition,totalLeftPosition,totalRightPosition;
totalLeftPosition=totalRightPosition=0;
TIMSK |= 0x01;
sei();
TCNT0 = 0; // only way to clear TOV0 at the moment
while(1){
readFIFOandAskForNextData(nunchukDATA);
if (!(nunchukDATA[5] & 1)) {// Z button
if (nunchukDATA[1]>0xA0) {vitesseDroite=vitesseGauche=nunchukDATA[1]-0x80; avant=1;}
else if (nunchukDATA[1]<0x60) {vitesseDroite=vitesseGauche=0x80-nunchukDATA[1];avant=0; }
else {vitesseGauche=vitesseDroite=0x00;}
if (nunchukDATA[0]>0xA0) vitesseDroite = vitesseDroite -nunchukDATA[0]+0x80;
else if (nunchukDATA[0]<0x60) vitesseGauche = vitesseGauche + nunchukDATA[0]-0x80;
if (avant) enAvant(vitesseGauche,vitesseDroite); else enArriere(vitesseGauche,vitesseDroite);
} else if (!(nunchukDATA[5] & 2)) {// C button
if (nunchukDATA[2]>0xB0) tourneDroite(0x55);
else if (nunchukDATA[2]<0x55) tourneGauche(0x55);
else if (nunchukDATA[3]>0x90) enAvant(0x55,0x55);
else if (nunchukDATA[3]<0x80) enArriere(0x55,0x55);
else enAvant(0x00,0x00);
}
else enAvant(0x00,0x00);
//PORTA = vitesseGauche;
leftPosition = PIND;
rightPosition = PINC;
PORTD = leftPosition;
totalLeftPosition += leftPosition;
totalRightPosition += rightPosition;
toPrint = totalLeftPosition;
toPrint <<= 8;
toPrint += totalRightPosition;
_delay_ms(100);
}
return 0;
}
Les sous-programmes manquants ont déjà été publiés dans cette page.
L'idée de ce programme est d'ajouter au fur et à mesure les valeurs des positions droites et gauches de les mettre dans une seule et même variable ("toPrint") qui sera affichée par l'interruption sur les 4 afficheurs 7 segments de la carte Basys 2.
Adapter CORDIC à notre problème
[modifier | modifier le wikicode]Nous allons nous poser la question de savoir comment adapter l'algorithme CORDIC à ce problème. En fait une question nous taraude depuis quelques temps. Nous la posons et tenterons de la résoudre plus tard (Lecteurs revenez de temps en temps voir où l’on en est ?).
Le radian comme le degré ne sont pas des bonnes unités d'angle pour nous. Elles nécessitent, en effet, toutes les deux des calculs arithmétiques compliqués (divisions). Notre unité sera plutôt un nombre de ticks sur une roue (pour parcourir une distance). Par exemple tourner de 90° nécessitera d'exécuter un certain nombre de ticks et c’est ce nombre qui doit devenir notre unité d'angle. Pas besoin de le diviser par l'entre-axe des roues (avantage sur le radian). La question est donc : Comment adapter la partie correspondante au calcul d'angle dans CORDIC à cette nouvelle unité ? et sa question corollaire Quelles sont les valeurs pré-calculées que l’on devra mettre en mémoire ? Si nous répondons correctement à ces deux questions, chacun d'entre-vous pourra l'adapter à son propre robot.
Prenons des exemples concrets
- pour le robot ASURO décrit dans un autre projet la rotation sur place de 90° se fait avec 27 ticks. Cette valeur nécessite 5 bits seulement, on est très loin des 16 bits du format Q3.13 ! Comment se traduira l'équation de récurrence sur les angles dans ce contexte ?
- pour le robot Digilent de ce chapitre, ce nombre de ticks est évalué à 114 (si rapport de réduction 1:19 [soit 1,5 tour x 19 x 4]). Un essai direct a donné entre 69 et 74, et on sera encore loin des 16 bits puisque 7 bits suffisent. Ici encore comment cela interviendra-t-il sur l'équation de récurrence des angles ?
Nous avons eu l'intuition par une nuit d'insomnie, qu’il fallait certainement reporter les 7 bits dans la partie haute (disons b13-b7) du format Q3.13 et compléter par des zéros sur les poids faibles (b6-b0). Tout cela en complément à deux. Si l’on s'arrête aux 6 bits initiaux on n'aura jamais assez d'itérations pour le calcul. Ceci ne reste qu'une intuition pour le moment et demande à être expérimenté.
Voir aussi
[modifier | modifier le wikicode]- Micro contrôleurs AVR, particulièrement les trois chapitres sur la robotique mobile.
- Projet pour lecture des capteurs Infra-Rouges pour l'année universitaire 2014/2015.
- Projet pour l'année scolaire2013/2014 rédigé par GALLAND Quentin, HENRY Guillaume et KERDILES Guillaume sur le WIKI de l'IUT de Troyes.
- Projet 2016-2017 rédigé très (trop) sommairement par Vin et Vilette qui sera repris plus tard.