Very High Speed Integrated Circuit Hardware Description Language/Les nouvelles interfaces : de la nunchuk de Nintendo à android
Dans ce chapitre nous avons l'intention d'étudier des interfaces un peu plus axées jeux que celles des chapitres précédents. Puisque nous avons l'intention de nous intéresser aux modestes jeux vidéo associés à des processeurs 8 bits nous voyons de la place pour étudier dans ce chapitre la manette de la console de jeux vidéo Wii de Nintendo. Un article publié dans Elektor (Septembre 2012) nous a décidé à nous intéresser à cette manette.
La deuxième extension possible est de s'intéresser aux téléphones portables et tablettes tactiles comme instrument de commande de jeux. D'un point de vue strictement technique ce choix peut sembler étrange : utiliser un téléphone portable (32 bits) pour commander un jeu vidéo qui tourne sur un processeur 8 bits ! Mais nous assumons ce choix : en fait nous sommes désespérés de voir nos effectifs étudiants baisser et cherchons à ce titre à avoir des démonstrations pour les portes ouvertes susceptibles d'intéresser nos jeunes. Est-ce le bon choix ? Seul l'avenir nous le dira.
Encore une fois ce chapitre est rédigé au fur et à mesure du déroulement des projets avec des étudiants. Cela peut le rendre parfois difficilement lisible puisque l'enseignant qui le rédige n'a aucun recul sur le sujet, mais espère que d'autres collègues et étudiants viendront à son secours.
Étude de la manette Nunchuk
[modifier | modifier le wikicode]La manette Nunchuk est destinée à commander les jeux de la console Wii. Pour nous, nous la destinons au jeu pacman présenté dans un autre chapitre. Nous avons l'intention de nous intéresser au Joystick analogique et laissons l'étude de l'accéléromètre pour une autre année (autrement dit elle sera ajoutée plus tard).
Nous allons essayer dans la mesure du possible de garder la connectique de la Nunchuk. Si vous décidez de couper le câble, vous devez respecter les couleurs suivantes :
- blanc = masse
- rouge = 3,3 V
- vert = données
- jaune = horloge
Si vous désirez garder le connecteur, voici sa documentation :
L'accéléromètre est positionné de manière perpendiculaire au Joystick. Cela permet ainsi facilement le remplacement du Joystick par l'accéléromètre, ce qui sera peut être expérimenté plus tard.
Avant de vous engager plus loin, la lecture du protocole I²C est vivement conseillée.
Le format des données
[modifier | modifier le wikicode]La manette Nunchuk envoie ses information comme 6 octets de données, lisibles à 0xa40008 et streamable en utilisant les modes Data Reporting qui incluent des octets d'extension (les octets non utilisés sont positionnés à 0x00). Les six octets de données sont (après decodage):
Bit | ||||||||
Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
0 | SX<7:0> | |||||||
1 | SY<7:0> | |||||||
2 | AX<9:2> | |||||||
3 | AY<9:2> | |||||||
4 | AZ<9:2> | |||||||
5 | AZ<1:0> | AY<1:0> | AX<1:0> | BC | BZ |
(tableau tiré de wiibrew)
SX,SY sont les positions du Joystick analogique en X et Y, tandis que AX, AY, et AZ sont les donnée de l'accéléromètre sur 10 bits.
Avant d’utiliser ces données, le programmeur doit faire l'opération suivante : donnée exacte = (donnée lue XOR 0x17) + 0x17. Comme nous le spécifierons plus loin, nous n'utiliserons pas cette opération : nous demanderons à la manette de nous retourner les données de manière non cryptée. Ceci se choisit avec l'initialisation du dialogue.
Le protocole d'échange
[modifier | modifier le wikicode]Le protocole d'échange est le protocole I²C. Pour le mettre en œuvre, il va nous falloir étudier un module VHDL et le transformer en périphérique. Avant cela, détaillons ce protocole.
Le protocole considère la manette Nunchuk comme esclave. Cet esclave a une adresse fixée par Nintendo de 0x52 (sur 7 bits). Ces 7 bits sont décalés vers le poids fort (d'une position) et on lui ajoute un bit de lecture/écriture (en poids faible) ce qui se transforme en deux identificateurs : écriture 0xA4 et lecture 0xA5.
Tout échange avec la manette nécessite une initialisation.
Protocole d'initialisation
[modifier | modifier le wikicode]Nos lectures nous ont conduit a découvrir deux initialisations différentes :
- PDF zx-nunchuk repris aussi dans un TP universitaire et ici aussi
- interface Nunchuk USB : Elektor (Septembre 2012) p16
Nous allons pour le moment présenter les deux techniques.
Initialisation courte (ancienne manière)
[modifier | modifier le wikicode]Un essai présenté ici (chez dangerousprototypes) avec un de leur produit, le bus pirate, pour épier des communications nous donne :
I2C>[0xa4 0x40 0x00]<<<Wii Nunchuck initialize I2C START CONDITION WRITE: 0xA4 GOT ACK: YES<<<write address WRITE: 0 × 40 GOT ACK: YES<<<write location (?) WRITE: 0 × 00 GOT ACK: YES<<<write 0 to location 0 × 40 (?) I2C STOP CONDITION I2C>
qui nous montre que l'initialisation consiste à envoyer 3 octets : 0xa4 0x40 0x00.
Si l’on veut un peu plus de détail, voici une représentation schématique (adaptée de PDF zx-nunchuk):
Nous avons adapté l'acquittement des trames directement à notre utilisation : c’est soit la manette qui acquitte soit le FPGA. Ici tout le monde peut se rendre compte que c’est toujours la manette.
Cette méthode ne fonctionne que sur les manettes de Nintendo filaires. Cela tombe bien c’est ce qui nous intéresse.
Initialisation à la Elektor (nouvelle méthode)
[modifier | modifier le wikicode]Cette initialisation est un peu plus complexe puisqu'elle se décompose par l'envoi de deux fois trois octets :
- envoi de trois octets : 0xA4 0xF0 0x55
- suivi encore de trois octets : 0xA4 0xFB 0x00
Cette méthode d'initialisation devrait fonctionner avec à peu près tous les matériels. Elle possède l'avantage de retourner les données non cryptées.
À part l'initialisation de la manette, il nous faut naturellement récupérer ses données de temps en temps.
La récupération des données
[modifier | modifier le wikicode]Pour récupérer les 6 octets de données présentés plus haut. Cela se fait en deux étapes :
- envoi du pointeur à lire à la manette, ici au début en 0x00
- récupération des six octets en envoyant la commande read à l'adresse 0x52, c'est-à-dire en envoyant l'octet 0xA5.
Voici ce que donne le bus pirate :
I2C>[0xa4 0x00]<<<setup the read pointer I2C START CONDITION WRITE: 0xA4 GOT ACK: YES<<<write address WRITE: 0 × 00 GOT ACK: YES<<<pointer to 0 I2C STOP CONDITION I2C>[0xa5 r:6]<<<read nunchuck measurements I2C START CONDITION WRITE: 0xA5 GOT ACK: YES<<<read address BULK READ 0 × 06 BYTES:<<read back 6 bytes 0 × 78 ACK 0x7A ACK 0x2F ACK 0x7D ACK 0x6E ACK 0 × 17 NACK I2C STOP CONDITION I2C>
Pour information les données retournées concernant le joystick (en tout cas celui que nous avons en notre possession) sont approximativement :
- SX retourne des données autour de 35 (complètement à gauche) jusqu'à 228(complètement à droite),
- SY retourne des données autour de 27 jusqu'à 220.
- au centre on a environ 128 pour les deux.
Résumé de la lecture
[modifier | modifier le wikicode]Voici présenté la trame d'initialisation du pointeur dans la RAM de la manette Nunchuk (adaptée de PDF zx-nunchuk) :
Cette trame est en général suivie par la commande de lecture (adaptée de PDF zx-nunchuk):
Conception du périphérique
[modifier | modifier le wikicode]Le périphérique destiné à communiquer avec la manette Nunchuk devra être réalisé à partir de projets existants. Citons par exemple :
- i2c Master Slave chez Opencores.org, projet réalisé par Eli Smertenko. Malheureusement ce cœur n'est absolument pas documenté, il faut donc se remonter les manches pour l’utiliser.
- conception d'un contrôleur I2C en VHDL
- PDF these magister qui décrit en français un contrôleur i2c en VHDL
Il nous faut choisir parmi ces trois possibilités. Nous avons essayé i2c Master Slave sans succès. Cela ne veut pas dire que le cœur ne fonctionne pas mais que nous n'avons pas trouvé les bonnes commandes avec notre machine d'états. Finalement nos recherches sur Internet nous ont permis de trouver un autre cœur I2C maître, avec une conception qui nous a plu... pour finir de le faire fonctionner au bout de trois semaines ! Celui-ci peut être trouvé ICI sur Internet
Notre i2c maître
[modifier | modifier le wikicode]La Nunchuk étant un périphérique i2c esclave, il nous faut un i2c maître pour dialoguer avec. Le maître est, entre autres, responsable de la réalisation de l'horloge, de la réalisation des "START" et "STOP".
Voici donc ce cœur i2c maître en VHDL (Auteur : à priori Richard Herveille, ancienne version de son cœur téléchargeable chez Opencores). Ce cœur est compatible wishbone ce qui ne nous intéresse pas puisque notre processeur ne l'est pas. Voici le code en question :
--
-- Simple I2C controller
--
-- 1) No multimaster
-- 2) No slave mode
-- 3) No fifo's
--
-- notes:
-- Every command is acknowledged. Do not set a new command before previous is acknowledged.
-- Dout is available 1 clock cycle later as cmd_ack
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
package I2C is
component simple_i2c is
port (
clk : in std_logic;
ena : in std_logic;
nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
-- input signals
start,
stop,
read,
write,
ack_in : in std_logic;
Din : in std_logic_vector(7 downto 0);
-- output signals
cmd_ack : out std_logic;
ack_out : out std_logic;
Dout : out std_logic_vector(7 downto 0);
-- i2c signals
SCL : inout std_logic;
SDA : inout std_logic
);
end component simple_i2c;
end package I2C;
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
entity simple_i2c is
port (
clk : in std_logic;
ena : in std_logic;
nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
-- input signals
start,
stop,
read,
write,
ack_in : in std_logic;
Din : in std_logic_vector(7 downto 0);
-- output signals
cmd_ack : out std_logic;
ack_out : out std_logic;
Dout : out std_logic_vector(7 downto 0);
-- i2c signals
SCL : inout std_logic;
SDA : inout std_logic
);
end entity simple_i2c;
architecture structural of simple_i2c is
component i2c_core is
port (
clk : in std_logic;
nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
cmd : in std_logic_vector(2 downto 0);
cmd_ack : out std_logic;
busy : out std_logic;
Din : in std_logic;
Dout : out std_logic;
SCL : inout std_logic;
SDA : inout std_logic
);
end component i2c_core;
-- commands for i2c_core
constant CMD_NOP : std_logic_vector(2 downto 0) := "000";
constant CMD_START : std_logic_vector(2 downto 0) := "010";
constant CMD_STOP : std_logic_vector(2 downto 0) := "011";
constant CMD_READ : std_logic_vector(2 downto 0) := "100";
constant CMD_WRITE : std_logic_vector(2 downto 0) := "101";
-- signals for i2c_core
signal core_cmd : std_logic_vector(2 downto 0);
signal core_ack, core_busy, core_txd, core_rxd : std_logic;
-- signals for shift register
signal sr : std_logic_vector(7 downto 0); -- 8bit shift register
signal shift, ld : std_logic;
-- signals for state machine
signal go, host_ack : std_logic;
begin
-- hookup i2c core
u1: i2c_core port map (clk, nReset, clk_cnt, core_cmd, core_ack, core_busy, core_txd, core_rxd, SCL, SDA);
-- generate host-command-acknowledge
cmd_ack <= host_ack;
-- generate go-signal
go <= (read or write) and not host_ack;
-- assign Dout output to shift-register
Dout <= sr;
-- assign ack_out output to core_rxd (contains last received bit)
ack_out <= core_rxd;
-- generate shift register
shift_register: process(clk)
begin
if (clk'event and clk = '1') then
if (ld = '1') then
sr <= din;
elsif (shift = '1') then
sr <= (sr(6 downto 0) & core_rxd);
end if;
end if;
end process shift_register;
--
-- state machine
--
statemachine : block
type states is (st_idle, st_start, st_read, st_write, st_ack, st_stop);
signal state : states;
signal dcnt : unsigned(2 downto 0);
begin
--
-- command interpreter, translate complex commands into simpler I2C commands
--
nxt_state_decoder: process(clk, nReset, state)
variable nxt_state : states;
variable idcnt : unsigned(2 downto 0);
variable ihost_ack : std_logic;
variable icore_cmd : std_logic_vector(2 downto 0);
variable icore_txd : std_logic;
variable ishift, iload : std_logic;
begin
-- 8 databits (1byte) of data to shift-in/out
idcnt := dcnt;
-- no acknowledge (until command complete)
ihost_ack := '0';
icore_txd := core_txd;
-- keep current command to i2c_core
icore_cmd := core_cmd;
-- no shifting or loading of shift-register
ishift := '0';
iload := '0';
-- keep current state;
nxt_state := state;
case state is
when st_idle =>
if (go = '1') then
if (start = '1') then
nxt_state := st_start;
icore_cmd := CMD_START;
elsif (read = '1') then
nxt_state := st_read;
icore_cmd := CMD_READ;
idcnt := "111";
else
nxt_state := st_write;
icore_cmd := CMD_WRITE;
idcnt := "111";
iload := '1';
end if;
end if;
when st_start =>
if (core_ack = '1') then
if (read = '1') then
nxt_state := st_read;
icore_cmd := CMD_READ;
idcnt := "111";
else
nxt_state := st_write;
icore_cmd := CMD_WRITE;
idcnt := "111";
iload := '1';
end if;
end if;
when st_write =>
if (core_ack = '1') then
idcnt := dcnt -1; -- count down Data_counter
icore_txd := sr(7);
if (dcnt = 0) then
nxt_state := st_ack;
icore_cmd := CMD_READ; -- for ack receive
else
ishift := '1';
-- icore_txd := sr(7);
end if;
end if;
when st_read =>
if (core_ack = '1') then
idcnt := dcnt -1; -- count down Data_counter
ishift := '1';
if (dcnt = 0) then
nxt_state := st_ack;
icore_cmd := CMD_WRITE;-- for ack send
icore_txd := ack_in;
end if;
end if;
when st_ack =>
if (core_ack = '1') then
-- generate command acknowledge signal
ihost_ack := '1';
-- Perform an additional shift, needed for 'read' (store last received bit in shift register)
ishift := '1';
-- check for stop; Should a STOP command be generated ?
if (stop = '1') then
nxt_state := st_stop;
icore_cmd := CMD_STOP;
else
nxt_state := st_idle;
icore_cmd := CMD_NOP;
end if;
end if;
when st_stop =>
if (core_ack = '1') then
nxt_state := st_idle;
icore_cmd := CMD_NOP;
end if;
when others => -- illegal states
nxt_state := st_idle;
icore_cmd := CMD_NOP;
end case;
-- generate registers
if (nReset = '0') then
core_cmd <= CMD_NOP;
core_txd <= '0';
shift <= '0';
ld <= '0';
dcnt <= "111";
host_ack <= '0';
state <= st_idle;
elsif (clk'event and clk = '1') then
if (ena = '1') then
state <= nxt_state;
dcnt <= idcnt;
shift <= ishift;
ld <= iload;
core_cmd <= icore_cmd;
core_txd <= icore_txd;
host_ack <= ihost_ack;
end if;
end if;
end process nxt_state_decoder;
end block statemachine;
end architecture structural;
--
--
-- I2C Core
--
-- Translate simple commands into SCL/SDA transitions
-- Each command has 5 states, A/B/C/D/idle
--
-- start: SCL ~~~~~~~~~~\____
-- SDA ~~~~~~~~\______
-- x | A | B | C | D | i
--
-- repstart SCL ____/~~~~\___
-- SDA __/~~~\______
-- x | A | B | C | D | i
--
-- stop SCL ____/~~~~~~~~
-- SDA ==\____/~~~~~
-- x | A | B | C | D | i
--
--- write SCL ____/~~~~\____
-- SDA ==X=========X=
-- x | A | B | C | D | i
--
--- read SCL ____/~~~~\____
-- SDA XXXX=====XXXX
-- x | A | B | C | D | i
--
-- Timing: Normal mode Fast mode
-----------------------------------------------------------------
-- Fscl 100KHz 400KHz
-- Th_scl 4.0us 0.6us High period of SCL
-- Tl_scl 4.7us 1.3us Low period of SCL
-- Tsu:sta 4.7us 0.6us setup time for a repeated start condition
-- Tsu:sto 4.0us 0.6us setup time for a stop condition
-- Tbuf 4.7us 1.3us Bus free time between a stop and start condition
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
USE ieee.std_logic_unsigned.all;
entity i2c_core is
port (
clk : in std_logic;
nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
cmd : in std_logic_vector(2 downto 0);
cmd_ack : out std_logic;
busy : out std_logic;
Din : in std_logic;
Dout : out std_logic;
SCL : inout std_logic;
SDA : inout std_logic
);
end entity i2c_core;
architecture structural of i2c_core is
constant CMD_NOP : std_logic_vector(2 downto 0) := "000";
constant CMD_START : std_logic_vector(2 downto 0) := "010";
constant CMD_STOP : std_logic_vector(2 downto 0) := "011";
constant CMD_READ : std_logic_vector(2 downto 0) := "100";
constant CMD_WRITE : std_logic_vector(2 downto 0) := "101";
type cmds is (idle, start_a, start_b, start_c, start_d, stop_a, stop_b, stop_c, rd_a, rd_b, rd_c, rd_d, wr_a, wr_b, wr_c, wr_d);
signal state : cmds;
signal SDAo, SCLo : std_logic;
signal txd : std_logic;
signal clk_en, slave_wait :std_logic;
signal cnt : unsigned(7 downto 0) := clk_cnt;
-- signal cnt : std_logic_vector(7 downto 0); -- := clk_cnt;
begin
-- whenever the slave is not ready it can delay the cycle by pulling SCL low
slave_wait <= '1' when ((SCLo = '1') and (SCL = '0')) else '0';
-- generate clk enable signal
gen_clken: process(clk, nReset)
begin
if (nReset = '0') then
cnt <= (others => '0');
clk_en <= '1'; --'0';
elsif (clk'event and clk = '1') then
if (cnt = 0) then
clk_en <= '1';
cnt <= clk_cnt;
else
if (slave_wait = '0') then
cnt <= cnt -1;
end if;
clk_en <= '0';
end if;
end if;
end process gen_clken;
-- generate statemachine
nxt_state_decoder : process (clk, nReset, state, cmd, SDA)
variable nxt_state : cmds;
variable icmd_ack, ibusy, store_sda : std_logic;
variable itxd : std_logic;
begin
nxt_state := state;
icmd_ack := '0'; -- default no acknowledge
ibusy := '1'; -- default busy
store_sda := '0';
itxd := txd;
case (state) is
-- idle
when idle =>
case cmd is
when CMD_START =>
nxt_state := start_a;
icmd_ack := '1'; -- command completed
when CMD_STOP =>
nxt_state := stop_a;
icmd_ack := '1'; -- command completed
when CMD_WRITE =>
nxt_state := wr_a;
icmd_ack := '1'; -- command completed
itxd := Din;
when CMD_READ =>
nxt_state := rd_a;
icmd_ack := '1'; -- command completed
when others =>
nxt_state := idle;
-- don't acknowledge NOP command icmd_ack := '1'; -- command completed
ibusy := '0';
end case;
-- start
when start_a =>
nxt_state := start_b;
when start_b =>
nxt_state := start_c;
when start_c =>
nxt_state := start_d;
when start_d =>
nxt_state := idle;
ibusy := '0'; -- not busy when idle
-- stop
when stop_a =>
nxt_state := stop_b;
when stop_b =>
nxt_state := stop_c;
when stop_c =>
-- nxt_state := stop_d;
-- when stop_d =>
nxt_state := idle;
ibusy := '0'; -- not busy when idle
-- read
when rd_a =>
nxt_state := rd_b;
when rd_b =>
nxt_state := rd_c;
when rd_c =>
nxt_state := rd_d;
store_sda := '1';
when rd_d =>
nxt_state := idle;
ibusy := '0'; -- not busy when idle
-- write
when wr_a =>
nxt_state := wr_b;
when wr_b =>
nxt_state := wr_c;
when wr_c =>
nxt_state := wr_d;
when wr_d =>
nxt_state := idle;
ibusy := '0'; -- not busy when idle
end case;
-- generate regs
if (nReset = '0') then
state <= idle;
cmd_ack <= '0';
busy <= '0';
txd <= '0';
Dout <= '0';
elsif (clk'event and clk = '1') then
if (clk_en = '1') then
state <= nxt_state;
busy <= ibusy;
txd <= itxd;
if (store_sda = '1') then
Dout <= SDA;
end if;
end if;
cmd_ack <= icmd_ack and clk_en;
end if;
end process nxt_state_decoder;
--
-- convert states to SCL and SDA signals
--
output_decoder: process (clk, nReset, state)
variable iscl, isda : std_logic;
begin
case (state) is
when idle =>
iscl := SCLo; -- keep SCL in same state
isda := SDA; -- keep SDA in same state
-- start
when start_a =>
iscl := SCLo; -- keep SCL in same state (for repeated start)
isda := '1'; -- set SDA high
when start_b =>
iscl := '1'; -- set SCL high
isda := '1'; -- keep SDA high
when start_c =>
iscl := '1'; -- keep SCL high
isda := '0'; -- sel SDA low
when start_d =>
iscl := '0'; -- set SCL low
isda := '0'; -- keep SDA low
-- stop
when stop_a =>
iscl := '0'; -- keep SCL disabled
isda := '0'; -- set SDA low
when stop_b =>
iscl := '1'; -- set SCL high
isda := '0'; -- keep SDA low
when stop_c =>
iscl := '1'; -- keep SCL high
isda := '1'; -- set SDA high
-- write
when wr_a =>
iscl := '0'; -- keep SCL low
-- isda := txd; -- set SDA
isda := Din;
when wr_b =>
iscl := '1'; -- set SCL high
-- isda := txd; -- set SDA
isda := Din;
when wr_c =>
iscl := '1'; -- keep SCL high
-- isda := txd; -- set SDA
isda := Din;
when wr_d =>
iscl := '0'; -- set SCL low
-- isda := txd; -- set SDA
isda := Din;
-- read
when rd_a =>
iscl := '0'; -- keep SCL low
isda := '1'; -- tri-state SDA
when rd_b =>
iscl := '1'; -- set SCL high
isda := '1'; -- tri-state SDA
when rd_c =>
iscl := '1'; -- keep SCL high
isda := '1'; -- tri-state SDA
when rd_d =>
iscl := '0'; -- set SCL low
isda := '1'; -- tri-state SDA
end case;
-- generate registers
if (nReset = '0') then
SCLo <= '1';
SDAo <= '1';
elsif (clk'event and clk = '1') then
if (clk_en = '1') then
SCLo <= iscl;
SDAo <= isda;
end if;
end if;
end process output_decoder;
SCL <= '0' when (SCLo = '0') else 'Z'; -- since SCL is externally pulled-up convert a '1' to a 'Z'(tri-state)
SDA <= '0' when (SDAo = '0') else 'Z'; -- since SDA is externally pulled-up convert a '1' to a 'Z'(tri-state)
-- SCL <= SCLo;
-- SDA <= SDAo;
end architecture structural;
Commentaires sur le code VHDL
[modifier | modifier le wikicode]Vous trouverez dans ce code souvent le commentaire : "-- 4x SCL". Ce commentaire est faux. En fait il faut réaliser 5x SCL à cause de l'état "idle" par lequel on passe systématiquement. Comme il y a plusieurs états idle, il s'agit de celui de "i2c_core". Nous avons pensé à cela dès que nous avons lu le code et cela a été confirmé par une étude à l'oscilloscope.
Cette entrée "4x SCL" nous a posé aussi un souci et nous ne savions pas exactement ce qu’il fallait y faire ! Au départ, nous nous sommes donc lancé avec un compteur 8 bits qui comptait à une fréquence "4x SCL". Mais une lecture approfondie du code nous a montré qu’il fallait en fait y mettre une valeur unique qui est chargée de temps en temps et qui est décrémentée et qui arrivée à zéro génère une validation : et c’est cette validation qui doit arriver tous les "4x SCL" euh pardon ! tous les "5x SCL". Cette valeur est positionnée dans le module de la section suivante à "01100100". Cherchons comment est calculée cette valeur ? On a une horloge de 50MHz soit 50 000 kHz et l’on doit fabriquer 5x 100 kHz (SCL est donc à 100 kHz). La division donne 100 qui en binaire donne 1100100. Voila donc l'explication.
Il est grand temps maintenant de présenter la partie que nous avons réalisé autour de ce cœur.
Notre machine d'états
[modifier | modifier le wikicode]Comme le titre l'indique, il s'agit d'une machine d'états que nous allons détailler quelque peu maintenant.
-- 30 Oct 2012 version 0.1
-- 11 décembre 2012 version 0.8
-- 17 décembre 2012 version 0.85
-- Serge MOUTOU
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;
USE ieee.std_logic_unsigned.all;
use work.I2C.all;
ENTITY nunchukTop IS
PORT (
sys_clk : IN std_logic;
Init : IN std_logic;
-- C_Button,Z_Button,'0','0',Up,Down,Left,Right
O_data_nunchuk : out std_logic_vector(7 downto 0);
-- i2c i/o
sda : INOUT std_logic;
-- scl better OUT : then change the i2c core
scl : INOUT std_logic);
END nunchukTop;
ARCHITECTURE arch of nunchukTop IS
COMPONENT seqNunchuk IS PORT (
clk, reset : IN std_logic;
start_ext, rec_ack,cmd_ack,i_timer : IN std_logic;
O_write,start,stop,O_read,O_ack_n : OUT std_logic;
write_Reg1,write_Reg2,write_Reg6,timer_init : OUT std_logic;
Dout : OUT std_logic_vector(7 DOWNTO 0)
);
END COMPONENT;
-- Declarations
SIGNAL s_start,s_stop,s_read,s_write,s_cmd_ack,s_rec_ack : std_logic;
SIGNAL s_write_Reg1,s_write_Reg2,s_write_Reg6,ss_Start,s_timer,s_timer_init,s_O_ack_n : std_logic;
SIGNAL Up, Down, Left, Right : std_logic;
SIGNAL Reg1,Reg2,Reg6,s_mstr_dout, s_din, startcmpt : std_logic_vector(7 DOWNTO 0);
SIGNAL timer : std_logic_vector(16 DOWNTO 0);
SIGNAL s_clk_cnt : std_logic_vector(7 downto 0);
SIGNAL s_clk_ucnt : unsigned(7 downto 0);
BEGIN
s_clk_cnt <= "01100100";--"01100100"= 5x100kHz il semble que c’est 5x qu’il faut générer
s_clk_ucnt <= unsigned(s_clk_cnt);
i1 : simple_i2c PORT MAP(clk => sys_clk,
ena => '1',
nReset => not init,
-- 00011111 = 4x400kHz, 01111101 = 4x100kHz
clk_cnt => s_clk_ucnt,--"01100100", s_clk_cnt: 5x SCL=100
ack_in => s_O_ack_n,
sda => sda,
scl => scl,
start => s_start, --in
stop => s_stop,
read => s_read,
write => s_write,
Din => s_din,
cmd_ack => s_cmd_ack,
ack_out => s_rec_ack,
Dout => s_mstr_dout
);
i2 : seqNunchuk PORT MAP( clk => sys_clk,
reset => init,
start_ext => ss_Start, --start automatique
start => s_start, --out
rec_ack => s_rec_ack,
cmd_ack => s_cmd_ack,
i_timer => s_timer, --out
O_ack_n => s_O_ack_n,
O_write => s_write,
stop => s_stop,
O_read => s_read,
Dout => s_din, --out
write_Reg1 => s_write_Reg1,
write_Reg2 => s_write_Reg2,
write_Reg6 => s_write_Reg6,
timer_init => s_timer_init
);
-- start managment
PROCESS(sys_clk,init) BEGIN
IF Init ='1' THEN startcmpt <= (OTHERS=>'0');
ELSIF rising_edge(sys_clk) THEN
startcmpt <= startcmpt + 1;
END IF;
END PROCESS;
ss_Start <= startcmpt(7);
-- timer managment
PROCESS(sys_clk) BEGIN
IF rising_edge(sys_clk) THEN
IF s_timer_init='1' THEN -- timer_resettoChange
timer <= (others => '0');
ELSE
timer <= timer +1;
END IF;
END IF;
END PROCESS;
s_timer <= timer(16);
-- three registers managment
PROCESS(sys_clk) BEGIN
IF rising_edge(sys_clk) THEN
IF Init ='1' THEN Reg1 <= "10000000";
ELSIF s_write_reg1='1' THEN
Reg1 <= s_mstr_dout;
END IF;
END IF;
END PROCESS;
PROCESS(sys_clk) BEGIN
IF rising_edge(sys_clk) THEN
IF s_write_reg2='1' THEN
Reg2 <= s_mstr_dout;
END IF;
END IF;
END PROCESS;
PROCESS(sys_clk) BEGIN
IF rising_edge(sys_clk) THEN
IF s_write_reg6='1' THEN
Reg6 <= s_mstr_dout;
END IF;
END IF;
END PROCESS;
-- ouput managment
PROCESS(Reg1) BEGIN
if Reg1 > x"C0" then
Right <= '1'; else Right <= '0';
end if;
if Reg1 < x"30" then
Left <= '1'; else Left <= '0';
end if;
END PROCESS;
PROCESS(Reg2) BEGIN
if Reg2 > x"C0" then
Up <= '1'; else Up <= '0';
end if;
if Reg2 < x"30" then
Down <= '1'; else Down <= '0';
end if;
END PROCESS;
-- C_Button,Z_Button,'0','0',Up,Down,Left,Right
-- construction de l'octet de sortie
O_data_nunchuk(7) <= Reg6(1); -- C button
O_data_nunchuk(6) <= Reg6(0); -- Z button
O_data_nunchuk(5) <= '0';
O_data_nunchuk(4) <= '0';
O_data_nunchuk(3) <= Up;
O_data_nunchuk(2) <= Down;
O_data_nunchuk(1) <= Left;
O_data_nunchuk(0) <= Right;
END arch;
LIBRARY ieee;
USE ieee.std_logic_1164.all;
ENTITY seqNunchuk IS PORT (
clk, reset : IN std_logic;
start_ext, rec_ack,cmd_ack,i_timer : IN std_logic;
O_write,start,stop,O_read,O_ack_n : OUT std_logic;
write_Reg1,write_Reg2,write_Reg6,timer_init : OUT std_logic;
Dout : OUT std_logic_vector(7 DOWNTO 0)
);
END seqNunchuk;
ARCHITECTURE arch_seqNunchuk OF seqNunchuk IS
TYPE nunchuk_state IS ( s0,s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,s15,s16,s17,s18,s19,s20,s21,s22);
SIGNAL state_reg, state_next : nunchuk_state;
--SIGNAL toWrite : std_logic_vector(7 DOWNTO 0);
BEGIN
PROCESS(clk,reset) BEGIN
IF RESET = '1' THEN
state_reg <= s0;
ELSIF rising_edge(clk) THEN
state_reg <= state_next;
END IF;
END PROCESS;
PROCESS(state_reg,start_ext,rec_ack,cmd_ack,i_timer) BEGIN
state_next <= state_reg; -- default value
CASE state_reg IS
WHEN s0 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF start_ext = '1' THEN
state_next <= s1;
ELSE
state_next <= s0;
END IF;
WHEN s1 => O_write <='1';start <='1';Dout <= x"A4";timer_init <= '0'; -- start fait le write
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s2;
ELSE
state_next <= s1;
END IF;
WHEN s2 => O_write <='1';start <='0';Dout <= x"F0";timer_init <= '0';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s3;
ELSE
state_next <= s2;
END IF;
WHEN s3 => O_write <='1';start <='0';Dout <= x"55";timer_init <= '1';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s4;
ELSE
state_next <= s3;
END IF;
WHEN s4 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF i_timer = '1' THEN
state_next <= s5;
ELSE
state_next <= s4;
END IF;
WHEN s5 => O_write <='1';start <='1';Dout <= x"A4";timer_init <= '0';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s6;
ELSE
state_next <= s5;
END IF;
WHEN s6 => O_write <='1';start <='0';Dout <= x"FB";timer_init <= '0';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s7;
ELSE
state_next <= s6;
END IF;
WHEN s7 => O_write <='1';start <='0';Dout <= x"00";timer_init <= '1';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s8;
ELSE
state_next <= s7;
END IF;
WHEN s8 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF i_timer = '1' THEN
state_next <= s9;
ELSE
state_next <= s8;
END IF;
WHEN s9 => O_write <='1';start <='1';Dout <= x"A4";timer_init <= '0';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s10;
ELSE
state_next <= s9;
END IF;
WHEN s10 => O_write <='1';start <='0';Dout <= x"00";timer_init <= '1';
O_read <= '0';stop<='1';O_ack_n<='1'; --stop=1 11/12/12
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s11;
ELSE
state_next <= s10;
END IF;
WHEN s11 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF i_timer = '1' THEN
state_next <= s12;
ELSE
state_next <= s11;
END IF;
WHEN s12 => O_write <='1';start <='1';Dout <= x"A5";timer_init <= '0';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s13;
ELSE
state_next <= s12;
END IF;
WHEN s13 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s20;
ELSE
state_next <= s13;
END IF;
WHEN s20 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg1<='1';write_Reg2<='0';write_Reg6<='0';
state_next <= s14;
WHEN s14 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s21;
ELSE
state_next <= s14;
END IF;
WHEN s21 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg1<='0';write_Reg2<='1';write_Reg6<='0';
state_next <= s16;
WHEN s16 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s17;
ELSE
state_next <= s16;
END IF;
WHEN s17 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s18;
ELSE
state_next <= s17;
END IF;
WHEN s18 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s19;
ELSE
state_next <= s18;
END IF;
WHEN s19 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '1';
O_read <= '1';stop<='1';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s22;
ELSE
state_next <= s19;
END IF;
WHEN s22 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='1';
state_next <= s15;
WHEN s15 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg1 <='0';write_Reg2 <='0';write_Reg6<='0';
IF i_timer = '1' THEN
state_next <= s9;--**s9 --s5; --s12;
ELSE
state_next <= s15;
END IF;
WHEN OTHERS => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
state_next <= s0;
END CASE;
END PROCESS;
END arch_seqNunchuk;
Ce code est un peu long et sera commenté un peu plus loin.
Pour ceux qui sont intéressés par ce code sachez qu’il a été publié ici le 11/12/12 mais qu’il risque d'évoluer un peu : la partie combinatoire sera revue plus tard... Revenez voir de temps en temps : nous retirerons cet avertissement dès que nous considèrerons que ce code est terminé.
- mis à jour le 17/12/12 pour gérer le bouton Z
- mis à jour le 16/01/2013 pour gérer le bouton C.
- ce code a été interfacé au pacman avec succès sauf pour les boutons C et Z, puis ...
- mis à jour dans une section un peu plus loin le 12 juillet 2013 avec un FIFO comme interface (semble fonctionner complètement maintenant)
- réalisation d'un périphérique i2c plus générique pour l'ATMega16 (décrit ICI) le 14 janvier 2017. Vous le trouverez dans la partie travaux pratiques de ce livre : Utiliser des shields Arduino.
Commentaires sur le code VHDL
[modifier | modifier le wikicode]Ce code nous montre que nous avons choisi l’initialisation longue qui a l'avantage de ne pas nécessiter de décodage de ce qui est reçu de la Nunchuk.
Cette machine d'états lit les six octets retournés par la Nunchuk mais ne mémorise que les deux premiers et le dernier. Les deux registres qui servent à mémoriser l'information du joystick analogique sont gérés par le code VHDL :
-- two registers managment
PROCESS(sys_clk) BEGIN
IF rising_edge(sys_clk) THEN
IF Init ='1' THEN Reg1 <= "10000000";
ELSIF s_write_reg1='1' THEN
Reg1 <= s_mstr_dout;
END IF;
END IF;
END PROCESS;
PROCESS(sys_clk) BEGIN
IF rising_edge(sys_clk) THEN
IF s_write_reg2='1' THEN
Reg2 <= s_mstr_dout;
END IF;
END IF;
END PROCESS;
L'initialisation d'un des deux registres (et pas des autres) est un vestige des tests et peut être retirée.
Nous avons choisi de sortir les informations "Up", "Down", "Left", "Right" car cette manette est destinée à remplacer un vieux Joystick à 4 interrupteurs qui nous fournissait donc une information binaire sur les quatre directions. Il n’est pas difficile à ce stade de lire les valeurs des accéléromètres mais nous laissons ce problème pour un projet d'une année future. La réalisation de ces quatre bits est faite par le circuit combinatoire :
-- ouput managment
PROCESS(Reg1) BEGIN
if Reg1 > x"C0" then
Right <= '1'; else Right <= '0';
end if;
if Reg1 < x"30" then
Left <= '1'; else Left <= '0';
end if;
END PROCESS;
PROCESS(Reg2) BEGIN
if Reg2 > x"C0" then
Up <= '1'; else Up <= '0';
end if;
if Reg2 < x"30" then
Down <= '1'; else Down <= '0';
end if;
END PROCESS;
Les valeurs C0 et 30 peuvent être changées si l’on a besoin de plus de sensibilité pour la manette. Ces valeurs C0 et 30 correspondent à la manette presque à fond de chaque côté mais nous permettent surtout de ne pas avoir deux déplacements à la fois. Si vous avez l'intention de faire un déplacement vers la droite, rien ne dit que vous n'allez pas légèrement déplacer le joystick analogique légèrement vers le haut (ou vers le bas). Il serait bon que dans ce cas précis que seul le déplacement vers la droite soit pris en compte : d'où les valeurs assez élevées (0xC0) et assez basses (0x30). Ces valeurs doivent être ajustées à vos manettes.
La numérotation des états de la machine d'états semble anarchique mais montre simplement les errements de l'auteur pour faire fonctionner l'ensemble.
Un nouveau module pour gérer l’ensemble des données de la Nunchuk
[modifier | modifier le wikicode]Le projet pacman 2013 doit utiliser l’ensemble des données de la manette Nunchuk y compris les données de l'accéléromètre. Nous publions le code dans ce chapitre mais il est parfaitement inutilisable sans microprocesseur embarqué puisque l'interface choisie est un FIFO. Au risque de me répéter, lisez pacman 2013 qui l’utilisera complètement (pas avant avril 2014 quand le projet sera terminé).
L'objectif de ce module est demander à la Nunchuk ses six données et de les mettre dans un FIFO. L'avantage est que du point de vue du futur processeur, ce module nécessitera une seule adresse de registre pour être lu.
-- 30 Oct 2012 v 0.9
-- 9th July 2013 v 1.0
-- managing all the data coming from Nunchuk in a FIFO
-- Serge MOUTOU
-- Le composant bbfifo_16x8 apparaissant dans ce code est celui
-- de Ken Chapman qui ne fonctionne que sur les Spartan3.
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;
USE ieee.std_logic_unsigned.all;
--USE ieee.numeric_std.all;
use work.I2C.all;
ENTITY nunchukTop IS
PORT (
sys_clk : IN std_logic;
Init : IN std_logic;
--NEW version 1.0 : command bus
readFIFO,i_continue : in std_logic;
-- OLD :C_Button,Z_Button,'0','0',Up,Down,Left,Right
-- NEW : FIFO data
O_data_nunchuk : out std_logic_vector(7 downto 0);
-- NEW : FIFO State
data_present, full, half_full : out std_logic;
-- i2c i/o
sda : INOUT std_logic;
-- scl better OUT : then change the i2c core
scl : INOUT std_logic);
END nunchukTop;
ARCHITECTURE arch of nunchukTop IS
COMPONENT seqNunchuk IS PORT (
clk, reset : IN std_logic;
start_ext, rec_ack,cmd_ack,i_timer,i_continue : IN std_logic;
O_write,start,stop,O_read,O_ack_n,O_init : OUT std_logic;
write_Reg,timer_init : OUT std_logic;
Dout : OUT std_logic_vector(7 DOWNTO 0)
);
END COMPONENT;
-- NEW version 1.0
component bbfifo_16x8 is
Port ( data_in : in std_logic_vector(7 downto 0);
data_out : out std_logic_vector(7 downto 0);
reset : in std_logic;
write : in std_logic;
read : in std_logic;
full : out std_logic;
half_full : out std_logic;
data_present : out std_logic;
clk : in std_logic);
end component bbfifo_16x8;
-- Declarations
SIGNAL s_start,s_stop,s_read,s_write,s_cmd_ack,s_rec_ack : std_logic;
SIGNAL s_write_Reg,ss_Start,s_timer,s_timer_init,s_O_ack_n : std_logic;
--SIGNAL Up, Down, Left, Right : std_logic;
SIGNAL s_init : std_logic;
SIGNAL s_mstr_dout, s_din, startcmpt : std_logic_vector(7 DOWNTO 0);
SIGNAL timer : std_logic_vector(16 DOWNTO 0);
SIGNAL s_clk_cnt : std_logic_vector(7 downto 0);
SIGNAL s_clk_ucnt : unsigned(7 downto 0);
BEGIN -- =50 car 25MHz
s_clk_cnt <= "00110010";--"01100100"= 5x100kHz il semble que c’est 5x qu’il faut générer
s_clk_ucnt <= unsigned(s_clk_cnt);
i1 : simple_i2c PORT MAP(clk => sys_clk,
ena => '1',
nReset => not init,
-- 00011111 = 4x400kHz, 01111101 = 4x100kHz
clk_cnt => s_clk_ucnt,--"01100100", s_clk_cnt: 5x SCL=100
ack_in => s_O_ack_n,
sda => sda,
scl => scl,
start => s_start, --in
stop => s_stop,
read => s_read,
write => s_write,
Din => s_din,
cmd_ack => s_cmd_ack,
ack_out => s_rec_ack,
Dout => s_mstr_dout
);
i2 : seqNunchuk PORT MAP( clk => sys_clk,
reset => init,
start_ext => ss_Start, --start automatique
i_continue => i_continue,
start => s_start, --out
rec_ack => s_rec_ack,
cmd_ack => s_cmd_ack,
i_timer => s_timer, --out
O_ack_n => s_O_ack_n,
O_write => s_write,
stop => s_stop,
O_read => s_read,
O_init => s_init,
Dout => s_din, --out
write_Reg => s_write_Reg,
timer_init => s_timer_init
);
i3 : bbfifo_16x8 PORT MAP( clk => sys_clk,
data_present=> data_present,
full => full,
half_full => half_full,
read => readFIFO,
reset => s_init,
data_out => O_data_Nunchuk,
data_in => s_mstr_dout,
write => s_write_reg
);
-- start managment
PROCESS(sys_clk,init) BEGIN
IF Init ='1' THEN startcmpt <= (OTHERS=>'0');
ELSIF rising_edge(sys_clk) THEN
startcmpt <= startcmpt + 1;
END IF;
END PROCESS;
ss_Start <= startcmpt(7);
-- timer managment
PROCESS(sys_clk) BEGIN
IF rising_edge(sys_clk) THEN
IF s_timer_init='1' THEN -- timer_resettoChange
timer <= (others => '0');
ELSE
timer <= timer +1;
END IF;
END IF;
END PROCESS;
s_timer <= timer(16);
END arch;
LIBRARY ieee;
USE ieee.std_logic_1164.all;
ENTITY seqNunchuk IS PORT (
clk, reset : IN std_logic;
start_ext, rec_ack,cmd_ack,i_timer,i_continue : IN std_logic;
O_write,start,stop,O_read,O_ack_n,O_init : OUT std_logic;
write_Reg,timer_init : OUT std_logic;
-- state_debug : OUT std_logic_vector(3 DOWNTO 0); --for debug purpose
Dout : OUT std_logic_vector(7 DOWNTO 0)
);
END seqNunchuk;
ARCHITECTURE arch_seqNunchuk OF seqNunchuk IS
TYPE nunchuk_state IS ( s0,s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12,s13,s14,
s15,s16,s17,s18,s19,s20,s21,s22,s23,s24,s25);
SIGNAL state_reg, state_next : nunchuk_state;
--SIGNAL toWrite : std_logic_vector(7 DOWNTO 0);
BEGIN
PROCESS(clk,reset) BEGIN
IF RESET = '1' THEN
state_reg <= s0;
ELSIF rising_edge(clk) THEN
state_reg <= state_next;
END IF;
END PROCESS;
PROCESS(state_reg,start_ext,rec_ack,cmd_ack,i_timer) BEGIN
state_next <= state_reg; -- default value
CASE state_reg IS
WHEN s0 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg<='0';O_init<='1';
IF start_ext = '1' THEN
state_next <= s1;
ELSE
state_next <= s0;
END IF;
WHEN s1 => O_write <='1';start <='1';Dout <= x"A4";timer_init <= '0'; -- start fait le write
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg<='0';O_init<='1';
IF cmd_ack = '1' THEN
state_next <= s2;
ELSE
state_next <= s1;
END IF;
WHEN s2 => O_write <='1';start <='0';Dout <= x"F0";timer_init <= '0';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg<='0';O_init<='1';
IF cmd_ack = '1' THEN
state_next <= s3;
ELSE
state_next <= s2;
END IF;
WHEN s3 => O_write <='1';start <='0';Dout <= x"55";timer_init <= '1';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg<='0';O_init<='1';
IF cmd_ack = '1' THEN
state_next <= s4;
ELSE
state_next <= s3;
END IF;
WHEN s4 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg<='0';O_init<='1';
IF i_timer = '1' THEN
state_next <= s5;
ELSE
state_next <= s4;
END IF;
WHEN s5 => O_write <='1';start <='1';Dout <= x"A4";timer_init <= '0';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg<='0';O_init<='1';
IF cmd_ack = '1' THEN
state_next <= s6;
ELSE
state_next <= s5;
END IF;
WHEN s6 => O_write <='1';start <='0';Dout <= x"FB";timer_init <= '0';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg<='0';O_init<='1';
IF cmd_ack = '1' THEN
state_next <= s7;
ELSE
state_next <= s6;
END IF;
WHEN s7 => O_write <='1';start <='0';Dout <= x"00";timer_init <= '1';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg<='0';O_init<='1';
IF cmd_ack = '1' THEN
state_next <= s8;
ELSE
state_next <= s7;
END IF;
WHEN s8 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg<='0';O_init<='1';
IF i_timer = '1' THEN
state_next <= s9;
ELSE
state_next <= s8;
END IF;
WHEN s9 => O_write <='1';start <='1';Dout <= x"A4";timer_init <= '0';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg<='0';O_init<='1';
IF cmd_ack = '1' THEN
state_next <= s10;
ELSE
state_next <= s9;
END IF;
WHEN s10 => O_write <='1';start <='0';Dout <= x"00";timer_init <= '1';
O_read <= '0';stop<='1';O_ack_n<='1'; --stop=1 11/12/12
write_Reg<='0';O_init<='0';
IF cmd_ack = '1' THEN
state_next <= s11;
ELSE
state_next <= s10;
END IF;
WHEN s11 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg<='0';O_init<='0';
IF i_timer = '1' THEN
state_next <= s12;
ELSE
state_next <= s11;
END IF;
WHEN s12 => O_write <='1';start <='1';Dout <= x"A5";timer_init <= '0';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg<='0';O_init<='0';
IF cmd_ack = '1' THEN
state_next <= s13;
ELSE
state_next <= s12;
END IF;
WHEN s13 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg<='0';O_init<='0';
IF cmd_ack = '1' THEN
state_next <= s20;
ELSE
state_next <= s13;
END IF;
WHEN s20 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg<='1';O_init<='0';
state_next <= s14;
WHEN s14 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg<='0';O_init<='0';
IF cmd_ack = '1' THEN
state_next <= s21;
ELSE
state_next <= s14;
END IF;
WHEN s21 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg<='1';O_init<='0';
state_next <= s16;
WHEN s16 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg<='0';O_init<='0';
IF cmd_ack = '1' THEN
state_next <= s23;
ELSE
state_next <= s16;
END IF;
WHEN s23 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg<='1';O_init<='0';
state_next <= s17;
WHEN s17 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg<='0';O_init<='0';
IF cmd_ack = '1' THEN
state_next <= s24;
ELSE
state_next <= s17;
END IF;
WHEN s24 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg<='1';O_init<='0';
state_next <= s18;
WHEN s18 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg<='0';O_init<='0';
IF cmd_ack = '1' THEN
state_next <= s25;
ELSE
state_next <= s18;
END IF;
WHEN s25 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg<='1';O_init<='0';
state_next <= s19;
WHEN s19 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '1';
O_read <= '1';stop<='1';O_ack_n<='1';
write_Reg<='0';O_init<='0';
IF cmd_ack = '1' THEN
state_next <= s22;
ELSE
state_next <= s19;
END IF;
WHEN s22 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg<='1';O_init<='0';
state_next <= s15;
WHEN s15 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg <='0';O_init<='0';
IF i_continue = '1' THEN
state_next <= s9;--**s9 --s5; --s12;
ELSE
state_next <= s15;
END IF;
WHEN OTHERS => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '0';stop<='1';O_ack_n<='1';O_init<='0';
write_Reg<='0';
state_next <= s0;
END CASE;
END PROCESS;
END arch_seqNunchuk;
Ce module utilise un FIFO logicore Xilinx qui présente plusieurs inconvénients :
- ce logicore n’est pas utilisable avec des FPGA non fournis par Xilinx. Sachez cependant, qu’il est facile de trouver l'équivalent en VHDL pur sur Internet
- on a un exemple de gaspillage de ressources ici : le FIFO est 16x8 alors qu'on utilisera seulement 6x8 données
- le signal data_present du FIFO est sorti du module et sera utilisé par le processeur. Or ce qu’il faudrait sortir c’est un signal "full" quand on a rempli nos six données. Nous espérons améliorer cela plus tard.
Fichier ucf
[modifier | modifier le wikicode]Le fichier ucf est relativement important ici. Il faut sortir l'i2c avec des résistances de tirage.
# inputs net "sys_clk" loc="T9"; net "Init" loc="L14"; # outputs net "Up" loc="L12"; # led2 net "Down" loc="P14"; # led1 net "Left" loc="N14"; # led3 net "Right" loc="K12"; # led0 net "O_ButtonZ" loc="P13"; #i2c inout net "sda" loc="D5"; #i2c data net "scl" loc="D6"; # i2c clock NET "sda" PULLUP; NET "scl" PULLUP; # sortie 7 segments net "s7segs<6>" loc="e14"; net "s7segs<5>" loc="g13"; net "s7segs<4>" loc="n15"; net "s7segs<3>" loc="p15"; net "s7segs<2>" loc="r16"; net "s7segs<1>" loc="f13"; net "s7segs<0>" loc="n16"; net "aff_sel<0>" loc="d14"; net "aff_sel<1>" loc="g14"; net "aff_sel<2>" loc="f14"; net "aff_sel<3>" loc="e13";
Résumé pour les utilisateurs de l'i2c
[modifier | modifier le wikicode]Les nombreux lecteurs de ce livre (le sont-il vraiment, nombreux ?) peuvent être attirés par l'aspect i2c mais sans la Nunchuk. Nous leur dédions ce petit résumé qui pourra, nous l'espérons, les faire réaliser des applications i2c dans leur FPGA.
Rappelons que la machine d'état partielle présentée ci-dessous fonctionne à 50 MHz.
Dans toute cette section nous avons choisi un style de programmation des machines d'états un peu lourd puisque sa partie combinatoire de sortie est systématiquement écrite pour chacun des états. Pour tous ces signaux intéressez-vous seulement à :
- O_write
- start
- Dout
- O_read
- stop
- O_ack_n
Tous sont des sorties de la machine d'états destinés à commander le cœur i2c.
- Le start ne fonctionne pas tout seul : il est toujours lié à une écriture par le maître. Le premier octet d'ailleurs qui est écrit à cette occasion est l'adresse de l'esclave. Il est réalisé dans notre cas par :
WHEN s1 => O_write <='1';start <='1';Dout <= x"A4";timer_init <= '0'; -- start fait le write
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s2;
ELSE
state_next <= s1;
END IF;
où vous voyez write et start positionnés en même temps ainsi que 0xA4 qui est l'adresse 0x52 décalée de un bit vers la gauche et avec le bit de poids faible à 0 qui signifie que l’on va continuer à écrire. Le code ci-dessous par contre est du même type sauf que dans 0xA5 le bit de poids faible est positionné ce qui signifie que l'état suivant, ici s13, va lire.
WHEN s12 => O_write <='1';start <='1';Dout <= x"A5";timer_init <= '0';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s13;
ELSE
state_next <= s12;
END IF;
- écriture de l'octet suivant : elle se fait par un code du type :
WHEN s6 => O_write <='1';start <='0';Dout <= x"FB";timer_init <= '0';
O_read <= '0';stop<='0';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s7;
ELSE
state_next <= s6;
END IF;
où seul write est positionné à 1.
- écriture du dernier octet : il est différent en ce sens qu’il est suivi par un STOP. Il faut alors positionner simultanément write et stop à 1.
WHEN s7 => O_write <='1';start <='0';Dout <= x"00";timer_init <= '1';
O_read <= '0';stop<='1';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s8;
ELSE
state_next <= s7;
END IF;
- lecture des octets : une fois demandé une lecture, les octets de données arrivent en rafale. C'est au maître de générer les acquittements et le "non acquittement" du dernier octet suivi du stop. Voici le code de lecture du premier octet :
WHEN s13 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s20;
ELSE
state_next <= s13;
END IF;
WHEN s20 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '0';
O_read <= '1';stop<='0';O_ack_n<='0';
write_Reg1<='1';write_Reg2<='0';write_Reg6<='0';
state_next <= s14;
Vous voyez une gestion de deux états. L'état s20 est destiné à mémoriser ce que l’on vient de lire dans un registre. ack_n = '0' signifie qu'on acquitte.
- lecture du dernier octet avec non acquittement suivi d'un STOP :
WHEN s19 => O_write <='0';start <='0';Dout <= x"00";timer_init <= '1';
O_read <= '1';stop<='1';O_ack_n<='1';
write_Reg1<='0';write_Reg2<='0';write_Reg6<='0';
IF cmd_ack = '1' THEN
state_next <= s15;
ELSE
state_next <= s19;
END IF;
read et stop sont positionné ainsi que "ack_n" à un, ce qui signifie pas d'acquittement.
Nous allons passer à un tout autre sujet maintenant, les téléphones Android.
Les difficultés rencontrées ne sont-elles pas résolues par d'autres
[modifier | modifier le wikicode]La réalisation du contrôleur maître pour la Nunchuk a été pour nous une épreuve... et nous savons que si en lieu et place de cette manette, nous désirons lire un accéléromètre MPU6050, par exemple, tout est à refaire ! Refaire la machine d'états n’est pas si simple et il faut prévoir ses tests. En clair nous cherchons depuis longtemps un moyen plus générique de faire les choses.
Nous avons trouvé un contrôleur i2c intelligent. Ce contrôleur développé par Mike Field est utilisé dans quelques uns de ses projets surtout en maître. Il l'a conçu pour éviter de réaliser justement des machines d'états complexes en remarquant que dans un FPGA, il n'y a pas lieu de faire tourner la machine d'état à une fréquence de 50 MHz puisqu'en général l'i2c plafonne à 1 MHz. Le problème est que le code VHDL disponible sur son site n'est pas complet et que Mike Field n'a jamais répondu à nos sollicitations. Comme il manque une instruction très importante pour l'i2c, nous avons tenté de compléter son code mais sans succès. C'est donc une autre voie qu'il nous faut suivre...
Après tout ces déboires et plusieurs années, notre expérience avec i2c dans les microcontrôleurs a un peu progressé. Lorsque nous avons réalisé le périphérique pour la Nunchuk en 2012, nous pensions que les périphériques i2c des microcontrôleurs étaient sophistiqués et qu'il serait difficile de les égaler. Mais la rédaction du chapitre sur l'i2C dans le livre sur les AVRs nous a montré que le matériel ne prenait pas grand chose en charge en fait. C'est au programmeur de gérer le protocole, même si ce n'est pas lui qui gère l'horloge. Nous avons donc décidé de copier partiellement la gestion de l'i2c des AVRs dans nos processeurs embarqués. Ce travail est réalisé dans la partie TP : Utiliser des Shields Arduino avec les FPGA.
Voir aussi
[modifier | modifier le wikicode]Le thème de l'i2c est abordé sous un autre angle (celui de l'esclave) dans un autre chapitre de ce livre :
- Utiliser un processeur externe
- Un autre périphérique i2c bien plus généraliste est réalisé dans un autre chapitre
- un contrôleur i2c intelligent
Interfacer un téléphone portable
[modifier | modifier le wikicode]Nous nous limiterons dans ce chapitre aux téléphones portables Android et laissons de côté les téléphones d'Apple.
L'évolution du marché des téléphones portables en fait un outil idéal de plus en plus utilisé comme entrée de commandes de montages électroniques. Besoin de vous convaincre ?
- une publicité Somfy de commande de volets roulants par téléphones portable durant l'été 2012
- Androled: pilotez votre électronique via le WIFI sous Android (elektor septembre 2012)
- AndroCar: véhicule piloté par l'inclinaison de votre téléphone (elektor octobre 2012)
- Vidéo YouTube (en français) d'un drone (AR.Drone de Parrot) commandé par téléphone portable
Ces exemples utilisent essentiellement le WIFI mais il existe d'autres moyens de communiquer. Pour l'année scolaire 2012/2013, nous avons décidé d’en explorer deux, avec toujours le même objectif, commander des jeux vidéo qui eux s'exécutent dans un FPGA. Voici ces deux méthodes :
- utiliser un kit adapté avec un PIC 24F qui gère une liaison USB maître sur laquelle on vient brancher le téléphone portable
- utiliser la liaison Bluetooth en interfaçant un module FB155BC de chez Firmetch pour un prix avoisinant les 30 €
La première façon de procéder est certainement la plus mauvaise d'un point de vue technique (pour notre problème bien sûr). En final on aura un processeur 32 bits (celui du téléphone), un processeur 16 bits (le PIC 24F du kit) et un processeur 8 bits dans le FPGA. Tout cela pour faire tourner un jeu des années 1980 (puisque c’est le processeur 8 bits qui l'exécute). Si la solution technique n’est pas bonne, le mot Android a suffi pour intéresser des étudiants à ce projet... La connexion USB maître reste intéressante à explorer d'un point de vue intellectuel de toute façon.
La deuxième façon de procéder est certainement plus naturelle puisqu'elle supprime l'étage du processeur 16 bits. Mais n'ayant que très peu d'expérience dans le Bluetooth, nous espérons ne pas tomber dans des pièges qui ne nous permettraient pas de finir le projet.
Écrire le programme Android
[modifier | modifier le wikicode]Le programme Android des deux applications est très similaire. Nous avons décidé de les examiner dans la même section tous les deux. L'application se présentera comme deux flèches sur un téléphone portable et l'appui sur les flèches déclenchera un envoi par USB pour l'un et un envoi par Bluetooth pour l'autre.
Nous n'avons pas l'intention de faire un cours sur Andoid dans cette section mais seulement de donner les programmes en les commentant. Vous pouvez lire (en) Andoid Software Development en attendant sa traduction en français.
Solution USB
[modifier | modifier le wikicode]La très grande majorité des téléphones portables ou des tablettes tactiles contiennent d'office une liaison USB. Un problème peut cependant éventuellement se poser pour les tablettes car certaines possèdent une liaison pouvant être soit maître soit esclave. Le kit choisi impose qu'on lui connecte une liaison USB esclave, puisque c’est lui qui joue le rôle du maître. C'est pour cela qu'un processeur 16 bits a été utilisé. À noter que chez Microchip un autre kit existe mettant en œuvre un processeur 32 bits. Chez son concurrent Atmel, seul un kit 32 bits est disponible, ce fabriquant ne disposant pas de processeurs 16 bits.
Solution Bluetooth
[modifier | modifier le wikicode]Cette solution nécessite un circuit spécialisé. Nous avons choisi un FB155BC de chez Firmetch mais il en existe bien d'autres. Ce circuit est alimenté en 3,3 V, comme notre FPGA. La communication entre ce circuit et le FPGA se fera par liaison série et commandes Hayes ou commandes AT.
Nous avons peu d'expérience dans la communication série avec les commandes Hayes. Pour expérimenter et en apprendre plus nous avons décidé d’utiliser le FPGA pour passer d'un hyperterminal sur un PC au circuit FB155BC. Cette solution peut paraitre étrange mais elle évite les conversions de tensions de la RS232 : en fait elles sont déjà réalisées dans la carte FPGA. Nous allons décrire en détail cette solution maintenant.
Connecter un PC en RS232 pour dialoguer
[modifier | modifier le wikicode]Par défaut le circuit est configuré en 9600 bauds, pas de parité, 1 bit de stop (sans protocole matériel).
Nous avons l'intention d’utiliser notre carte FPGA comme interface entre un PC qui exécute un hyperterminal et la carte Bluetooth. Cette méthode va nous permettre d'expérimenter doucement comment se passe la connexion entre notre téléphone portable et notre carte Bluetooth. En fait tout ceci est relativement simple à réaliser et nous allons le documenter maintenant.
La série de commandes Hayes donnée ci-après n'est valable que pour le circuit Firmtech FB155BX. Pour les circuits concurrents chercher dans leur documentation propre.
Voici donc une série de commandes importantes explorées avec notre hyperterminal.
Donner un nom au périphérique
[modifier | modifier le wikicode]Tout périphérique Bluetooth possède une adresse et un nom. Son adresse ne peut pas être changée mais ce n’est pas le cas de son nom. La commande qui permet de donner un nom est :
- Envoyer AT+BTNAME=xxxxxxxxxxxx
où xxxxxxxxxxxx est à remplacer par un nom. On aura par exemple :
- Envoyer AT+BTNAME=test↵
- Recevoir OK
À partir de maintenant le Firmtech sera vu sous le nom "test" si l’on fait une recherche des périphériques bluetooth à proximité. Cette recherche est naturellement réalisée par le téléphone ou la tablette Android.
Le mode esclave
[modifier | modifier le wikicode]Voici le dialogue destiné à passer le circuit Firmtech FB155BX en mode esclave :
- Envoyer “AT&F” au circuit Bluetooth
- “OK” doit être reçu comme réponse du Firmtech FB155BX
- le circuit FB155BX redémarre
- “BTWIN Slave mode start” doit être reçu comme deuxième réponse du Firmtech FB155BX
- “OK” doit être reçu comme troisième réponse du Firmtech FB155BX
Le mode maître
[modifier | modifier le wikicode]Voici le dialogue destiné à passer le circuit Firmtech FB155BX en mode maître :
- Envoyer “AT+BTROLE=M” au circuit Bluetooth
- “OK” doit être reçu comme réponse du Firmtech FB155BX
- Envoyer “ATZ” au circuit Bluetooth
- le circuit FB155BX redémarre
- “BTWIN Master mode start” doit être reçu comme deuxième réponse du Firmtech FB155BX
- “OK” doit être reçu comme troisième réponse du Firmtech FB155BX
Retour au mode esclave
[modifier | modifier le wikicode]Voici le dialogue destiné à repasser le circuit Firmtech FB155BX en mode esclave :
- Envoyer “AT+BTROLE=S” au circuit Bluetooth
- “OK” doit être reçu comme réponse du Firmtech FB155BX
- Envoyer “ATZ” au circuit Bluetooth
- le circuit FB155BX redémarre
- “BTWIN Slave mode start” doit être reçu comme deuxième réponse du Firmtech FB155BX
- “OK” doit être reçu comme troisième réponse du Firmtech FB155BX
Recherche en mode maître
[modifier | modifier le wikicode]Voici le dialogue destiné à passer le circuit Firmtech FB155BX en mode recherche à partir du mode maître :
- Envoyer “AT+BTINQ?” au circuit Bluetooth
- “OK” doit être reçu comme réponse du Firmtech FB155BX puis peu de temps après :
- des informations sont envoyées sur les périphériques bluetooth proches sont envoyées
- “OK” doit être reçu comme réponse du Firmtech FB155BX en final
Les informations peuvent être décomposées en :
- adresse, par exemple “001901000002”,
- un nom de périphérique, par exemple “FB155v2.2.0”
- une classe de périphérique, par exemple “1F00”.
Connexion au périphérique en mode maître
[modifier | modifier le wikicode]Une fois les informations trouvées, il vous suffit de réaliser :
- Envoyer “ATD001901000002” au circuit Bluetooth
- “OK” doit être reçu comme réponse du Firmtech FB155BX puis peu de temps après :
- “CONNECT 001901000002” doit être reçu du Firmtech FB155BX.
Déconnexion du périphérique
[modifier | modifier le wikicode]Voici le dialogue destiné à déconnecter le circuit Firmtech FB155BX
- Envoyer “+++” au circuit Bluetooth
- “OK” doit être reçu comme réponse du Firmtech FB155BX
- Envoyer “ATH” au circuit Bluetooth
- “OK” doit être reçu comme réponse du Firmtech FB155BX
- “DISCONNECT” doit être reçu comme réponse du Firmtech FB155BX
La mise en place de ces protocoles par un processeur embarqué dans un FPGA est présentée dans le lien projet 2012/2013.
Nous nous sommes intéressé jusqu'à présent au mode maître mais nous allons essentiellement travailler en mode esclave : c’est le téléphone ou la tablette qui seront maître.
Accepter une connexion en mode esclave
[modifier | modifier le wikicode]Il faut d’abord donner un nom à notre périphérique, nom qui sera utilisé dans la liste des périphériques bluetooth détecté (par le téléphone ou la tablette).
- Envoyer “AT+BTSCAN” au circuit Bluetooth au départ. Cela permet au Bluetooth de l'Android de détecter le Fimtech
- Choisir le périphérique correspondant au Firmtech, puis entrer le mot de passe sur le téléphone (1234)
et la connexion est faite. Comme vous pouvez le constater la connexion en mode esclave est très simple et ne nécessite donc pas d'analyse complexe des chaines reçues en réponse. Voici comment on peut envisager les choses par exemple avec des programmes en c (détaillés dans un autre chapitre) :
// bluetooth ready for connexion
usart_send2('A');usart_send2('T');usart_send2('+');usart_send2('B');usart_send2('T');
usart_send2('S');usart_send2('C');usart_send2('A');usart_send2('N');usart_send2(0x0D);
Et voici comment on lit les informations :
ch = usart_receive2();
// if (bit_is_set(PINB,PINB6)) if (raqG_y<182) delta_raqG_y=1;
// if (bit_is_set(PINB,PINB7)) if (raqG_y>0) delta_raqG_y=-1;
if (ch == 'd') if (raqG_y<182) delta_raqG_y=1;
if (ch == 'u') if (raqG_y>0) delta_raqG_y=-1;
Lire le chapitre correspondant où le Bluetooth est utilisé pour faire bouger une raquette d'un jeu de Pong.
Exploration de l'ADK
[modifier | modifier le wikicode]Lors de la conférence Google I/O 2011, a été annoncé l’arrivée de l’Android Open Accessory Development Kit (ADK). Il permet de rajouter une connectivité physique à un périphérique Android à l’aide d’une carte électronique, un peu comme le IOIO. La partie matérielle correspond à shield Arduino USB Maitre. Cette nouvelle fonctionnalité a été intégrée dans Android 3.1 et 2.3.4.
Cette solution sera explorée plus tard, mais pour l'instant, vous pouvez en chercher une description dans une section suivante "Voir aussi" en fin de chapitre.
Ce chapitre nous a amené à nous intéresser à des interfaces nouvelles. Pour les explorer de manière différente, nous allons nous intéresser pour finir ce chapitre à la communication entre un micro-contrôleur et un FPGA. Il s'agit d'un vaste sujet que nous allons aborder par des projets.
Voir aussi
[modifier | modifier le wikicode]- Un autre périphérique i2c bien plus généraliste est réalisé dans un autre chapitre de ce livre
- D'autres utilisations de l'i2c sont évoquées dans la partie travaux pratiques de ce livre
- Le protocole I²C
- conception d'un contrôleur I2C en VHDL
- PDF thèse magister : Description en français d'un contrôleur i2c en VHDL
- (en) Nunchuck (site qui utilise une orthographe avec un "c" en avant-dernière lettre)
- (en) PDF zx-nunchuk
- (en) Programming AVR-i2c Interface
- (en) Bus pirate et wii
- (en) un contrôleur i2c intelligent
- (en) Bluetooth et android
- Open Silicium n°5 (Janv/Fev/Mars 2012), numéro dédié au Bluetooth, revue française disponible en PDF moyennant 9 €
- Android open accessory development kit de google (fr) est une analyse de l'ADK.
- IOIO une carte pour connecter un périphérique Android avec un nouveau monde électronique basée sur un PIC24F