Very High Speed Integrated Circuit Hardware Description Language/Utiliser un processeur externe
Nous allons présenter dans ce chapitre un certain nombre de solutions consistant à déporter le processeur vers l'extérieur. Quand nous avons commencé l'écriture de ce livre nous ne pensions pas en arriver là... mais quelques prises de consciences nous ont fait changer d'avis :
- la baisse continue des prix des microcontrôleurs du commerce. On trouve maintenant des platines de développement 16/32 bits à moins de 20 € (chez Texas Instrument notamment en 2015) et moins de 10 € en 2022 pour un ESP32 malgré la crise des semi-conducteurs du Covid en 2020.
- la réalisation de projets autour de la robotique mobile nous amène à augmenter la taille de notre processeur. Ainsi un processeur 32 bits permet de faire assez facilement des calculs en nombre flottant.
- une étude de 2008 (EETimes 30 juillet 2008) montrait que seulement 1/3 des utilisations de FPGA utilisant un processeur utilisait ce processeur dans le FPGA. Les choses ont probablement changées depuis : il semble que l’on soit passé à 50% en 2014.
L'étude de EETimes ci-dessus tentait d'examiner des pistes pour interfacer les deux mondes du micro-contrôleur et celui du FPGA en soulignant qu’à priori aucun des deux n'est prévu pour cela.
Nous allons examiner dans ce chapitre les communications entre un processeur du commerce et le FPGA qui ne sera considéré donc que comme un périphérique. Dire périphérique ne veut pas dire sans aucune intelligence. Ainsi un petit processeur pourra se glisser dans ce FPGA et être responsable de la communication avec le processeur externe !
Les communications que nous envisageons d'étudier pour le moment sont le protocôle SPI et le protocole i2c. Le domaine des 32 bits sera peut être approfondi un jour prochain avec le bus External Bus Interface (EIB) que l’on peut rencontrer sur ce type de micro-contrôleur de type ARM.
Pour rendre ce chapitre intéressant à un public le plus diversifié possible, nous sommes obligés d’utiliser plusieurs architectures différentes allant du 8 au 32 bits. Nous espérons que, même si vous ne disposez que d'une architecture différente, vous trouviez ici suffisamment d'informations pour un portage utile pour vous. Nous rappelons que vous pouvez aussi rédiger un chapitre ou une simple section concernant votre architecture... ce qui pourrait être utile à d'autres lecteurs.
Les connecteurs PMod
[modifier | modifier le wikicode]Les FPGA que nous utilisons sont sur des cartes d'évaluation réalisées par Digilent. Elles sont caractérisées par des extensions appelées PMod que nous allons présenter maintenant.
Utilisation simple des PMod
[modifier | modifier le wikicode]Parmi les périphériques que l’on peut acheter pour les FPGA, il en existe un certain nombre avec des connecteurs PMod. Ils sont alors très faciles à mettre en œuvre avec ou sans câble. Le seul point qu’il faut surveiller est de ne pas le brancher à l’envers. Pour s'y repérer il suffit de savoir que d'un côté du connecteur vous avez tout au bout Vcc et à côté la masse gnd. Si vous repérez cela des deux côtés, c’est gagné. La photo ci-dessous, par exemple, montre la connexion de deux périphériques de puissance (non visibles) à la carte FPGA. Allez voir dans le chapitre Commande de robot mobile et périphériques associés d'autres photos disponibles.
Utilisation avec périphérique non adapté
[modifier | modifier le wikicode]Nous avons déjà publié dans le chapitre Commande de robot mobile et périphériques associés une série de photos montrant l’utilisation d'un PMod pour connecter une manette Nunchuk. Nous en reprenons une ci-dessous.
Vu le positionnement de notre connecteur blanc sur le PMod, l'alimentation Vcc = 3,3 V se trouve à gauche du connecteur lorsqu'on le regarde de face. De toute façon, il est bon de se rappeler que Vcc est en bout. C'est le fil orange de la photo.
Puisque nous commençons à aborder les notions de droite et gauche des connecteurs PMods, vous devez garder à l'esprit que cette notion est définie quand on regarde le PMod de l'extérieur vers la carte FPGA, comme par exemple sur la photo ci-dessus.
Lorsque le connecteur PMod est double (c'est souvent le cas sur les cartes plus récentes comme Nexys 3, la nouvelle Basys 3 et d'autres encore) les deux Vcc sont superposés (en face à gauche).
Utilisation avec plaque à essai
[modifier | modifier le wikicode]Cette façon de faire est à conseiller lorsque vous chercher à réaliser une connexion entre un de vos propre périphérique et une carte FPGA.
La tension d'alimentation des FPGA est 3,3 V. Ne présentez jamais une tension de 5V sur une entrée FPGA. |
Cette utilisation ne nécessite pas d'explication supplémentaire sinon que le mieux est d’utiliser des fils de connexions Digilent, ceux que l’on voit sur la photo entre la Basys2 et les commandes des moteurs. L'achat de périphériques PMod vous fournit automatiquement ce genre de connectique. Si vous n'avez pas l'intention d'investir dans des périphériques PMod, utilisez de simples câbles males-males pour plaques à essais.
Trouver les contraintes à mettre dans le fichier ucf
[modifier | modifier le wikicode]Les fichiers ucf que l’on télécharge dans la page des platines FPGA sont suffisamment commentés pour être facilement utilisables. La convention pour les PMods doubles est que l’on commence par le haut (et du côté opposé à Vcc). Par exemple, si vous avez :
##JA #NET "JA<0>" LOC = "T12" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L19P, Sch name = JA1 #NET "JA<1>" LOC = "V12" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L19N, Sch name = JA2 #NET "JA<2>" LOC = "N10" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L20P, Sch name = JA3 #NET "JA<3>" LOC = "P11" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L20N, Sch name = JA4 #NET "JA<4>" LOC = "M10" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L22P, Sch name = JA7 #NET "JA<5>" LOC = "N9" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L22N, Sch name = JA8 #NET "JA<6>" LOC = "U11" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L23P, Sch name = JA9 #NET "JA<7>" LOC = "V11" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L23N, Sch name = JA10
alors JA<0> est en haut côté droit du connecteur PMod (lorsqu'on est en face), JA<1> est un cran vers la gauche ... JA<4> est en bas à droite ... et JA<7> est en bas à gauche avant la masse.
|
Si on vous dit que le câblage du PMod a été choisi comme (voir plus loin par exemple) :
SPI | -- | -- | MOSI | MISO | SCK | SS |
---|---|---|---|---|---|---|
PMOD | 12 (Vcc) | 11 (GND) | 10 | 9 | 8 | 7 |
cela veut dire que SS est en broche 7 du connecteur Pmod, c'est-à-dire en bas à droite.
Processeur ATMega avec Arduino et SPI
[modifier | modifier le wikicode]Nous commençons par utiliser l'Arduino parce que c’est probablement une des architectures les plus utilisées actuellement parmi les architectures modestes de 8 bits. Elle est caractérisée par une facilité exemplaire de programmation et des prix attractifs (autour de 20 €). Ce prix ne fera que descendre dans les années suivantes.
Présentation de l'Arduino utilisé
[modifier | modifier le wikicode]L'Arduino en général est décrit aussi dans un chapitre du livre sur les AVR de la Wikiversité. Mais il est décliné en plusieurs versions.
Nous avons choisi la Platine "Pro Micro - 3,3 V/8MHz" de chez Sparkfun. Ce choix est essentiellement lié au fait que la platine est en 3,3 V et donc directement compatible avec le FPGA, ce qui n’est pas le cas de la majorité des platines Arduino. L'inconvénient associé à cette baisse de tension d'alimentation est le passage de sa fréquence d'horloge de 16 à 8 MHz, ce qui reste largement suffisant pour ce que nous prévoyons de faire dans cette section.
Vous ne possédez pas cette platine
[modifier | modifier le wikicode]Si vous avez déjà une platine Arduino, elle est probablement en 5V, donc pas compatible au niveau des tensions avec le FPGA. Ne vous découragez pas pour autant, les tensions peuvent être adaptées.
Les sorties du micro-contrôleur peuvent être adaptées avec un simple diviseur de tension avec deux résistances (10k et 20k) pour ne pas détériorer le FPGA. La sortie du FPGA peut être branchée directement en entrée du micro-contrôleur mais cela nécessite un test pour être sûr que ce dernier considère le 3,3 V comme un 1 logique.
On peut aussi réaliser une adaptation bidirectionnelle. Ce genre d'adaptation est décrit par exemple dans ce lien. Considérez particulièrement celle qui utilise un FET (2N7000) et deux résistance de 10k, adaptation que nous avons eu l’occasion de tester.
La conversion analogique/numérique
[modifier | modifier le wikicode]Une des applications pratiques à l'interface FPGA micro-contrôleur est l’utilisation des convertisseurs analogiques numériques présents dans la majorité de ces derniers. C'est ce que nous avions l'intention de réaliser dans le projet PMod Processeur pour une liaison série sur la Basys 2. Notez que cette technique existe aussi dans certaines cartes FPGA comme la MOJO 3.0. En effet cette carte est architecturée autour d'un ATMega32U4 et d'un FPGA Spartan6. Il est possible de récupérer les valeurs des convertisseurs Analogiques Numériques dans le FPGA.
Utiliser le langage Arduino pour lire une valeur analogique est très simple. Il existe une primitive appelée "analogRead". Rappelons pour être complet que les convertisseurs des ATMega sont sur 10 bits.
Complément sur l'installation Linux
[modifier | modifier le wikicode]Le site de Sparkfun, le hookup Guide n’est pas complet pour la programmation de la Platine "Pro Micro - 3,3 V/8MHz". Ajouter les cartes sparkfun à l'aide du fichier fourni ne suffit pas. Des lignes sont à mettre dans le fichier "46-arduino.rules" dans /etc/udev/rules.d :
ACTION!="add|change", GOTO="mm_usb_device_blacklist_local_end" SUBSYSTEM!="usb", GOTO="mm_usb_device_blacklist_local_end" ENV{DEVTYPE}!="usb_device", GOTO="mm_usb_device_blacklist_local_end" ATTRS{idVendor}=="1b4f" ATTRS{idProduct}=="9204", ENV{ID_MM_DEVICE_IGNORE}="1" ATTRS{idVendor}=="1b4f" ATTRS{idProduct}=="9203", ENV{ID_MM_DEVICE_IGNORE}="1" LABEL="mm_usb_device_blacklist_local_end"
Suivies par un
sudo service udev restart
elles permettent une programmation et une utilisation de la liaison série.
Depuis la rédaction de cette section, nous avons rencontré une installation Linux Ubuntu qui n'a pas nécessité ces changements... contrairement à notre version un peu plus ancienne. A vous de faire les tests correspondants...
Présentation du protocole SPI
[modifier | modifier le wikicode]La programmation SPI dans le monde Arduino est facile. Mais sa mise en œuvre avec un FPGA nécessite une bonne compréhension de ce protocole.
L'article Serial Peripheral Interface de Wikipédia présente le protocole correspondant.
Des étudiants de l'IUT de Troyes ont travaillé sur ce type de projet et ont écrit quelques lignes ici.
La programmation SPI
[modifier | modifier le wikicode]La programmation SPI du côté Arduino ne présente donc pas de difficulté particulière.
Voici donc un programme Maître (langage C) d'exemple :
#include <SPI.h>
//**** Version pour Sparkfun Pro-micro {{Unité|3.3|{{Abréviation|V|volt}}}}
//**** Horloge 8MHz
char dataToSend=0xA5;
void setup (void) {
// Put SCK, MOSI, SS pins into output mode
// also put SCK, MOSI into LOW state, and SS into HIGH state.
pinMode(10,OUTPUT); // SS not dedicated
pinMode(16,OUTPUT); // MOSI
pinMode(14,INPUT); // MISO
// ensure SS stays high for now
digitalWrite(10, HIGH);
// Then put SPI hardware into Master mode and turn SPI on
SPI.begin ();
// Slow down the master a bit
SPI.setClockDivider(SPI_CLOCK_DIV2);
//little indian or big indian ?
// SPI.setBitOrder(MSBFIRST);
// SPI.setBitOrder(LSBFIRST);
// SPI.setDataMode(SPI_MODE0);
delay(100);
} // end of setup
void loop (void) {
//déclaration variable
unsigned char recievedData;
// enable Slave Select (SS is pin 10)
digitalWrite(10, LOW);
// transmission réception
recievedData=SPI.transfer(dataToSend);
delay(10);
digitalWrite(10, HIGH);
// 1 seconds delay
delay(1000);
dataToSend++;
} // end of loop
Il peut être utilisé pour un début d'exploration.
Si vous voulez tester la communication bidirectionnelle du SPI, remplacez la boucle ci-dessus par celle-ci :
void loop (void) {
//déclaration variable
unsigned char recievedData;
// enable Slave Select
digitalWrite(10, LOW);
// SS is pin 10
recievedData=SPI.transfer(dataToSend);
// Serial.println(dataByte,DEC);
delay(10);
digitalWrite(10, HIGH);
dataToSend = recievedData;
delay(1000);
// 1 seconds delay
}
Cette version bidirectionnelle avec une division seulement par deux de l'horloge a été testée en échange SPI avec une Nexys 3 avec succès (recopie des interrupteurs sur les LEDs avec une seconde de retard à cause du delay qui peut être naturellement supprimé).
Nous n'avons pas réussi à faire fonctionner le transfert bidirectionnel avec
recievedData=SPI.transfer(recievedData);
mais seulement en recopiant la variable de réception recievedData dans la variable à envoyer dataToSend (comme dans le programme juste au-dessus de cette remarque).
Examinons donc maintenant ce que nous devons faire côté FPGA pour y réaliser un esclave SPI.
Esclave SPI sur FPGA
[modifier | modifier le wikicode]Nous avons décidé de ne pas construire notre propre esclave mais d’en chercher un sur opencore : SPI Master Slave de Jonny Doin. Téléchargez-le. Nous avons ensuite écrit un composant général contenant cet esclave que voici :
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.std_logic_unsigned.all;
entity arduino_fpga is port (
clk_100MHz : in std_logic; --100 MHz car carte Nexys 3 utilisée : à adapter pour vous
leds8 : out std_logic_vector(7 downto 0);
-- Port SPI
mosi : in std_logic;
miso : out std_logic;
ss : in std_logic;
sck : in std_logic;
sw : in std_logic_vector(7 downto 0)
);
end arduino_fpga;
architecture arch of arduino_fpga is
component spi_slave is
Generic (
N : positive := 32; -- 32bit serial word length is default
CPOL : std_logic := '0'; -- SPI mode selection (mode 0 default)
CPHA : std_logic := '0'; -- CPOL = clock polarity, CPHA = clock phase.
PREFETCH : positive := 3); -- prefetch lookahead cycles
Port (
clk_i : in std_logic := 'X'; -- internal interface clock (clocks di/do registers)
spi_ssel_i : in std_logic := 'X'; -- spi bus slave select line
spi_sck_i : in std_logic := 'X'; -- spi bus sck clock (clocks the shift register core)
spi_mosi_i : in std_logic := 'X'; -- spi bus mosi input
spi_miso_o : out std_logic := 'X'; -- spi bus spi_miso_o output
di_req_o : out std_logic; -- preload lookahead data request line
di_i : in std_logic_vector (N-1 downto 0) := (others => 'X'); -- parallel load data in (clocked in on rising edge of clk_i)
wren_i : in std_logic := 'X'; -- user data write enable
wr_ack_o : out std_logic; -- write acknowledge
do_valid_o : out std_logic; -- do_o data valid strobe, valid during one clk_i rising edge.
do_o : out std_logic_vector (N-1 downto 0);
-- parallel output (clocked out on falling clk_i)
--- debug ports: can be removed for the application circuit ---
do_transfer_o : out std_logic; -- debug: internal transfer driver
wren_o : out std_logic; -- debug: internal state of the wren_i pulse stretcher
rx_bit_next_o : out std_logic; -- debug: internal rx bit
state_dbg_o : out std_logic_vector (3 downto 0); -- debug: internal state register
sh_reg_dbg_o : out std_logic_vector (N-1 downto 0) -- debug: internal shift register
);
end component;
--signal sdo,sleds8,ssleds8 : std_logic_vector(7 downto 0);
--signal s_wren_i : std_logic;
begin
ic1 : spi_slave generic map (N => 8)
port map ( clk_i => clk_100MHz,
do_o(7 downto 0) => leds8(7 downto 0),
di_i => sw,
spi_ssel_i => ss,
spi_sck_i => sck,
spi_mosi_i => mosi,
spi_miso_o => miso,
wren_i => not ss--s_wren_i
);
end arch;
Ce qui est donné ci-dessus permet un échange bidirectionnel entre l'Arduino Maître et le FPGA esclave.
Le fichier de contrainte correspondant est (pour une Nexys3) présenté dans la boite déroulante ci-dessous :
## Clock signal NET "CLK_100MHz" LOC = "V10" | IOSTANDARD = "LVCMOS33"; #Bank = 2, pin name = IO_L30N_GCLK0_USERCCLK, Sch name = GCLK Net "CLK_100MHz" TNM_NET = sys_clk_pin; TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin 100000 kHz; ## Leds NET "leds8<0>" LOC = "U16" | IOSTANDARD = "LVCMOS33"; NET "leds8<1>" LOC = "V16" | IOSTANDARD = "LVCMOS33"; NET "leds8<2>" LOC = "U15" | IOSTANDARD = "LVCMOS33"; NET "leds8<3>" LOC = "V15" | IOSTANDARD = "LVCMOS33"; NET "leds8<4>" LOC = "M11" | IOSTANDARD = "LVCMOS33"; NET "leds8<5>" LOC = "N11" | IOSTANDARD = "LVCMOS33"; NET "leds8<6>" LOC = "R11" | IOSTANDARD = "LVCMOS33"; NET "leds8<7>" LOC = "T11" | IOSTANDARD = "LVCMOS33"; ## Switches NET "sw<0>" LOC = "T10" | IOSTANDARD = "LVCMOS33"; NET "sw<1>" LOC = "T9" | IOSTANDARD = "LVCMOS33"; NET "sw<2>" LOC = "V9" | IOSTANDARD = "LVCMOS33"; NET "sw<3>" LOC = "M8" | IOSTANDARD = "LVCMOS33"; NET "sw<4>" LOC = "N8" | IOSTANDARD = "LVCMOS33"; NET "sw<5>" LOC = "U8" | IOSTANDARD = "LVCMOS33"; NET "sw<6>" LOC = "V8" | IOSTANDARD = "LVCMOS33"; NET "sw<7>" LOC = "T5" | IOSTANDARD = "LVCMOS33"; ##JC ##Connecteur haut pour Tiva C LaunchPad #NET "ss" LOC = "H3" | IOSTANDARD = "LVCMOS33"; #NET "sck" CLOCK_DEDICATED_ROUTE = FALSE | LOC = "L7" | IOSTANDARD = "LVCMOS33"; #NET "miso" LOC = "K6" | IOSTANDARD = "LVCMOS33"; #NET "mosi" LOC = "G3" | IOSTANDARD = "LVCMOS33"; #Connecteur bas pour Pro Micro Sparkfun NET "ss" LOC = "G1" | IOSTANDARD = "LVCMOS33"; NET "sck" CLOCK_DEDICATED_ROUTE = FALSE |LOC = "J7" | IOSTANDARD = "LVCMOS33"; NET "miso" LOC = "J6" | IOSTANDARD = "LVCMOS33"; NET "mosi" LOC = "F2" | IOSTANDARD = "LVCMOS33";
Il vous montre que l’on a utilisé le connecteur PMod nommé JC (partie basse).
Le câblage du PMod a été choisi comme :
SPI | -- | -- | MOSI | MISO | SCK | SS |
---|---|---|---|---|---|---|
PMOD | 12 (Vcc) | 11 (GND) | 10 | 9 | 8 | 7 |
et nous avons utilisé la partie inférieure du connecteur en reliant la masse mais pas la source de tension : le FPGA et l'Arduino ont tous les deux leur propre alimentation par l'USB.
Le site de Sparkfun, le hookup Guide, vous permet de déduire une connexion que l’on représente dans un tableau qui met en correspondance les broches du Sparkfun Pro Micro avec le PMOD de Digilent pour un bon fonctionnement.
Sparkfun Pro micro | -- | GND | 16 | 14 | 15 | 10 |
---|---|---|---|---|---|---|
PMOD (Digilent) | 12 (Vcc) | 11 (GND) | 10 | 9 | 8 | 7 |
En clair les deux tableaux peuvent être fusionnés comme ceci :
SPI | -- | -- | MOSI | MISO | SCK | SS |
---|---|---|---|---|---|---|
PMOD | 12 (Vcc) | 11 (GND) | 10 | 9 | 8 | 7 |
Sparkfun Pro micro | -- | GND | 16 | 14 | 15 | 10 |
Ce tableau signifie que la broche Arduino (Sparkfun) qui porte le numéro 10 est reliée à la broche numéro 7 du PMod. Cette broche est à l'opposé de Vcc sur le connecteur PMod inférieur.
Vous serez obligé d'adapter la partie PMOD à votre carte FPGA et à votre choix de PMOD dans votre fichier ucf. Il est donné plus haut mais nous rappelons la partie concernée :
##JC #Connecteur bas pour Pro Micro Sparkfun NET "ss" LOC = "G1" | IOSTANDARD = "LVCMOS33"; NET "sck" CLOCK_DEDICATED_ROUTE = FALSE |LOC = "J7" | IOSTANDARD = "LVCMOS33"; NET "miso" LOC = "J6" | IOSTANDARD = "LVCMOS33"; NET "mosi" LOC = "F2" | IOSTANDARD = "LVCMOS33";
Le connecteur PMod sur la carte Nexys 3 qui nous a servi à faire les essais est double : une partie haute et une partie basse. C'est pour cela qu’apparaît "Connecteur bas" dans les commentaires de ce fichier ucf.
Le "CLOCK_DEDICATED_ROUTE" apparaissant dans le fichier ucf signifie que l'horloge fabriquée par l'Arduino maître va être directement utilisée dans le FPGA comme horloge (c'est-à-dire avec des "if rising_edge(sck)"). Or cette horloge ne sera pas unique : s'il y a un processeur dans le fpga il aura aussi la sienne. Ces deux horloges n'ayant aucun rapport entre elles doivent être alors maniées avec précautions. Le terme générique pour désigner ces problème est clock domain crossing en anglais et nous ne savons pas traduire exactement cette expression (traversée des domaines d'horloge peut-être).
Pour éviter ce genre de problème on échantillonne l'horloge en principe. Tel n'a pas été le choix de l'auteur du module SPI utilisé. Il peut ainsi fonctionner à plus grande vitesse. Mais pour le cœur i2c utilisé plus loin, l'échantillonnage a été réalisé. Normal la fréquence d'horloge de l'i2c est souvent 100 kHz.
Projets associés
[modifier | modifier le wikicode]- PMod Processeur pour une liaison série sur la Basys 2 décrit un projet qui associe l'Arduino Pro Micro de Sparkfun et une Basys2 en vue d'une installation sur une base robotique mobile
Processeur ATMega avec Arduino et protocole I2C
[modifier | modifier le wikicode]Nous utiliserons encore la Platine "Pro Micro - 3,3 V/8MHz" de chez Sparkfun et restons donc dans le monde Arduino.
Nous allons nous intéresser maintenant au protocole I2C. L'intérêt de ce protocole par rapport au SPI est la notion d'adresse : chaque périphérique a une adresse particulière. Cela permet de réaliser plusieurs périphériques dans notre FPGA et de les adresser individuellement. En principe le SPI nécessite une ligne SS (Slave Select) par esclave et cela prend vite des broches. L'étude de la manette Nunchuk dans ce livre utilisait le FPGA comme i2c maître alors que nous allons plutôt l’utiliser en esclave dans cette section.
- i2c master slave de Eli Smertenko chez Opencores en VHDL, mais contrairement à ce que suggère son nom l'esclave n’est pas implanté en tant que module séparé
- i2c slave est un esclave i2c chez Opencores écrit en Verilog
- et un autre esclave i2c lui aussi en Verilog
Le dernier esclave est trop rudimentaire pour être utilisé, le premier nécessite un travail important. Nous choisissons donc le deuxième même s'il est en Verilog. Rappelons à ce propos que nous avons commencé à écrire un chapitre sur le sujet (mariage VHDL et Verilog) qui n’est pas encore très avancé. Le deuxième a été choisi aussi parce qu’il dispose de 4 octets en entrée et de 4 octets en sortie. Cela nous semble suffisant même si l'exemple donné plus loin n'en utilise qu'un seul en entrée et sortie.
Pour information la librairie Arduino pour l'i2c fixe la fréquence du maître à 100 kHz. Si vous voulez une fréquence plus grande il faudra :
- changer la fréquence dans la librairie Arduino
- ou utiliser votre propre librairie C
Le câblage a été choisi comme :
I2C | -- | -- | -- | -- | SCL | SDA |
---|---|---|---|---|---|---|
Sparkfun Pro micro | -- | GND | -- | -- | 3 | 2 |
PMOD (Digilent) | 12 (Vcc) | 11 (GND) | 10 | 9 | 8 | 7 |
Programmes Arduino de lecture/écriture pour l'esclave FPGA
[modifier | modifier le wikicode]Nous allons distinguer dans un premier temps la lecture et l'écriture.
Écrire une valeur sur les LEDs
[modifier | modifier le wikicode]La situation est donc la suivante : on dispose d'un cœur FPGA réalisant un esclave i2c d'adresse 0x3C. Il est câblé de telle manière qu'un de ses registres de sortie (il peut en avoir jusque 4) est relié à des leds.
Le programme suivant :
#include <Wire.h>
void setup() {
Wire.begin(); // join i2c bus (address optional for master)
}
byte x = 0;
void loop(){
// transmit to device 0x3C (adresse choisie en dur dans le cœur FPGA)
Wire.beginTransmission(0x3C);
// transmet l'adresse d'écriture qui varie entre 0 et 3 :
Wire.write(0);
Wire.write(x); // sends one byte
Wire.endTransmission(); // stop transmitting
x++;
delay(500);
}
fonctionne parfaitement avec le cœur FPGA décrit plus loin.
Nous avons passé beaucoup de temps pour ajouter le Wire.write(0) qui est absolument obligatoire à cause du fait que le premier registre dans lequel on écrit est en adresse 0.
Écrire une valeur sur les LEDs en C pur
[modifier | modifier le wikicode]Nous avons testé le programme ci-dessous avec succès. Il utilise une librairie simple écrite par Peter Fleury :
/*************************************************************************
* Title: I2C master library using hardware TWI interface
* Author: Peter Fleury <pfleury@gmx.ch> http://jump.to/fleury
* File: $Id: twimaster.c,v 1.3 2005/07/02 11:14:21 Peter Exp $
* Software: AVR-GCC 3.4.3 / avr-libc 1.2.3
* Target: any AVR device with hardware TWI
* Usage: API compatible with I2C Software Library i2cmaster.h
**************************************************************************/
#include <inttypes.h>
#include <compat/twi.h>
//#include "i2cmaster.h"
#undef F_CPU
#define F_CPU 8000000UL
#include <util/delay.h>
/* I2C clock in Hz */
#define SCL_CLOCK 100000L
/*************************************************************************
Initialization of the I2C bus interface. Need to be called only once
*************************************************************************/
void i2c_init(void)
{
/* initialize TWI clock: 100 kHz clock, TWPS = 0 => prescaler = 1 */
TWSR = 0; /* no prescaler */
TWBR = ((F_CPU/SCL_CLOCK)-16)/2; /* must be > 10 for stable operation */
}/* i2c_init */
/*************************************************************************
Issues a start condition and sends address and transfer direction.
return 0 = device accessible, 1= failed to access device
*************************************************************************/
unsigned char i2c_start(unsigned char address)
{
uint8_t twst;
// send START condition
TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
// wait until transmission completed
while(!(TWCR & (1<<TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ( (twst != TW_START) && (twst != TW_REP_START)) return 1;
// send device address
TWDR = address;
TWCR = (1<<TWINT) | (1<<TWEN);
// wail until transmission completed and ACK/NACK has been received
while(!(TWCR & (1<<TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ( (twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK) ) return 1;
return 0;
}/* i2c_start */
/*************************************************************************
Terminates the data transfer and releases the I2C bus
*************************************************************************/
void i2c_stop(void)
{
/* send stop condition */
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
// wait until stop condition is executed and bus released
while(TWCR & (1<<TWSTO));
}/* i2c_stop */
/*************************************************************************
Send one byte to I2C device
Input: byte to be transfered
Return: 0 write successful
1 write failed
*************************************************************************/
unsigned char i2c_write( unsigned char data )
{
uint8_t twst;
// send data to the previously addressed device
TWDR = data;
TWCR = (1<<TWINT) | (1<<TWEN);
// wait until transmission completed
while(!(TWCR & (1<<TWINT)));
// check value of TWI Status Register. Mask prescaler bits
twst = TW_STATUS & 0xF8;
if( twst != TW_MT_DATA_ACK) return 1;
return 0;
}/* i2c_write */
int main() {
unsigned char x=0;
i2c_init();
while(1) {
//decalage de l'adresse de 1 bit vers la gauche pour laisser bit R/W à 0
i2c_start(0x3C<<1);
i2c_write(0);
i2c_write(x);
i2c_stop();
x++;
_delay_ms(500);
}
return 0;
}
Il est naturellement possible de mélanger le langage Arduino avec cette librairie, par exemple comme ceci :
void setup(){
i2c_init();
}
void loop() {
static unsigned char x=0;
i2c_start(0x3C<<1);
i2c_write(0);
i2c_write(x);
i2c_stop();
x++;
delay(500);
}
Pour ceux qui veulent la librairie complète, la voici :
/*************************************************************************
* Title: I2C master library using hardware TWI interface
* Author: Peter Fleury <pfleury@gmx.ch> http://jump.to/fleury
* File: $Id: twimaster.c,v 1.3 2005/07/02 11:14:21 Peter Exp $
* Software: AVR-GCC 3.4.3 / avr-libc 1.2.3
* Target: any AVR device with hardware TWI
* Usage: API compatible with I2C Software Library i2cmaster.h
**************************************************************************/
#include <inttypes.h>
#include <compat/twi.h>
#include "i2cmaster.h"
/* I2C clock in Hz */
#define SCL_CLOCK 100000L
/*************************************************************************
Initialization of the I2C bus interface. Need to be called only once
*************************************************************************/
void i2c_init(void)
{
/* initialize TWI clock: 100 kHz clock, TWPS = 0 => prescaler = 1 */
TWSR = 0; /* no prescaler */
TWBR = ((F_CPU/SCL_CLOCK)-16)/2; /* must be > 10 for stable operation */
}/* i2c_init */
/*************************************************************************
Issues a start condition and sends address and transfer direction.
return 0 = device accessible, 1= failed to access device
*************************************************************************/
unsigned char i2c_start(unsigned char address)
{
uint8_t twst;
// send START condition
TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
// wait until transmission completed
while(!(TWCR & (1<<TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ( (twst != TW_START) && (twst != TW_REP_START)) return 1;
// send device address
TWDR = address;
TWCR = (1<<TWINT) | (1<<TWEN);
// wail until transmission completed and ACK/NACK has been received
while(!(TWCR & (1<<TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ( (twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK) ) return 1;
return 0;
}/* i2c_start */
/*************************************************************************
Issues a start condition and sends address and transfer direction.
If device is busy, use ack polling to wait until device is ready
Input: address and transfer direction of I2C device
*************************************************************************/
void i2c_start_wait(unsigned char address)
{
uint8_t twst;
while ( 1 )
{
// send START condition
TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
// wait until transmission completed
while(!(TWCR & (1<<TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ( (twst != TW_START) && (twst != TW_REP_START)) continue;
// send device address
TWDR = address;
TWCR = (1<<TWINT) | (1<<TWEN);
// wail until transmission completed
while(!(TWCR & (1<<TWINT)));
// check value of TWI Status Register. Mask prescaler bits.
twst = TW_STATUS & 0xF8;
if ( (twst == TW_MT_SLA_NACK )||(twst ==TW_MR_DATA_NACK) )
{
/* device busy, send stop condition to terminate write operation */
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
// wait until stop condition is executed and bus released
while(TWCR & (1<<TWSTO));
continue;
}
//if( twst != TW_MT_SLA_ACK) return 1;
break;
}
}/* i2c_start_wait */
/*************************************************************************
Issues a repeated start condition and sends address and transfer direction
Input: address and transfer direction of I2C device
Return: 0 device accessible
1 failed to access device
*************************************************************************/
unsigned char i2c_rep_start(unsigned char address)
{
return i2c_start( address );
}/* i2c_rep_start */
/*************************************************************************
Terminates the data transfer and releases the I2C bus
*************************************************************************/
void i2c_stop(void)
{
/* send stop condition */
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
// wait until stop condition is executed and bus released
while(TWCR & (1<<TWSTO));
}/* i2c_stop */
/*************************************************************************
Send one byte to I2C device
Input: byte to be transfered
Return: 0 write successful
1 write failed
*************************************************************************/
unsigned char i2c_write( unsigned char data )
{
uint8_t twst;
// send data to the previously addressed device
TWDR = data;
TWCR = (1<<TWINT) | (1<<TWEN);
// wait until transmission completed
while(!(TWCR & (1<<TWINT)));
// check value of TWI Status Register. Mask prescaler bits
twst = TW_STATUS & 0xF8;
if( twst != TW_MT_DATA_ACK) return 1;
return 0;
}/* i2c_write */
/*************************************************************************
Read one byte from the I2C device, request more data from device
Return: byte read from I2C device
*************************************************************************/
unsigned char i2c_readAck(void)
{
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);
while(!(TWCR & (1<<TWINT)));
return TWDR;
}/* i2c_readAck */
/*************************************************************************
Read one byte from the I2C device, read is followed by a stop condition
Return: byte read from I2C device
*************************************************************************/
unsigned char i2c_readNak(void)
{
TWCR = (1<<TWINT) | (1<<TWEN);
while(!(TWCR & (1<<TWINT)));
return TWDR;
}/* i2c_readNak */
Lecture de données en provenance de l'esclave
[modifier | modifier le wikicode]L'esclave est un FPGA pour lequel 4 registres sont disponibles en lecture. Le programme suivant :
#include <Wire.h>
void setup() {
Wire.begin(); // join i2c bus (address optional for master)
Serial.begin(9600); // start serial for output
}
void loop() {
Wire.beginTransmission(0x3C);
Wire.write(4); // addresse du premier registre à lire
Wire.requestFrom(0x3C, 4); // request 4 bytes from slave device 0x3C
while(Wire.available()) // slave may send less than requested
{
char c = Wire.read(); // receive a byte as character
Serial.println(c,HEX); // print the character
}
Wire.endTransmission();
delay(2000);
}
permet de récupérer les valeurs des interrupteurs et les valeurs câblées en dur dans notre périphérique i2c.
Une question vient peut-être à l'esprit du lecteur : comment réaliser Wire.requestFrom(0x3C, 4); avec la librairie C ?
Voici donc un exemple qui mélange le langage Arduino et le C (surtout pour avoir à disposition la liaison série) :
void setup(){
i2c_init();
Serial.begin(9600);
}
void loop() {
static unsigned char x=0;
unsigned char c;
// on commence par dire que l’on va lire à partir de l'adresse 4
i2c_start(0x3C<<1);
i2c_write(4);
i2c_stop();
// on dit ensuite que l’on va lire 4 octets
i2c_start((0x3C<<1));
i2c_write(4);
i2c_stop();
// on envoie ensuite un ordre de lecture
i2c_start((0x3C<<1)|0x01);
// et on lit maintenant
c=i2c_readAck();
Serial.print(c,HEX);
c=i2c_readAck();
Serial.print(c,HEX);
c=i2c_readAck();
Serial.print(c,HEX);
c=i2c_readNak();
Serial.println(c,HEX);
// on termine le tout
i2c_stop();
x++;
delay(2000);
}
Hormis un petit problème sur notre carte FPGA avec un bouton switch, tout semble fonctionner normalement.
Voir aussi
[modifier | modifier le wikicode]
Depuis le temps que nous en parlons, il est temps maintenant de présenter notre cœur FPGA comme esclave i2c.
Un Cœur FPGA pour réaliser un esclave I2C (en Verilog)
[modifier | modifier le wikicode]Le cœur FPGA est celui de chez Opencores (i2c slave) avec comme seul changement la fréquence d'horloge de notre Nexys 3 à 100 MHz dans i2c_slave_define.v et un module global dans i2cSlaveTop.v :
`include "i2cSlave_define.v"
module i2cSlaveTop (
clk,
rst,
sda,
scl,
sw,
leds8
);
input clk;
input rst;
inout sda;
input [7:0] sw;
input scl;
output [7:0] leds8;
i2cSlave u_i2cSlave(
.clk(clk),
.rst(rst),
.sda(sda),
.scl(scl),
.myReg0(leds8),
.myReg1(),
.myReg2(),
.myReg3(),
.myReg4(sw),
.myReg5(8'h34),
.myReg6(8'h56),
.myReg7(8'h78)
);
endmodule
On a donc modifié l'original comme suit :
- remplacer reg0 par leds8 qui pour nous sera directement relié aux 8 leds de notre carte FPGA
- ajouté sw comme premier registre d'entrée qui sera directement relié à nos interrupteurs de la carte Nexys 3
Pour information, l'adresse du périphérique est fixée en dur dans un fichier et non pas au moment de l'instanciation. Cela ne permet donc pas d’utiliser deux périphériques i2c dans un même FPGA sans changement... C'est un peu dommage, mais nous changerons peut-être cela dans le futur...
Le fichier ucf correspondant montre que l’on utilise les switchs comme entrée et les 8 leds comme sortie.
## Clock signal NET "CLK" LOC = "V10" | IOSTANDARD = "LVCMOS33"; #Bank = 2, pin name = IO_L30N_GCLK0_USERCCLK, Sch name = GCLK Net "CLK" TNM_NET = sys_clk_pin; TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin 100000 kHz; ## Leds NET "leds8<0>" LOC = "U16" | IOSTANDARD = "LVCMOS33"; NET "leds8<1>" LOC = "V16" | IOSTANDARD = "LVCMOS33"; NET "leds8<2>" LOC = "U15" | IOSTANDARD = "LVCMOS33"; NET "leds8<3>" LOC = "V15" | IOSTANDARD = "LVCMOS33"; NET "leds8<4>" LOC = "M11" | IOSTANDARD = "LVCMOS33"; NET "leds8<5>" LOC = "N11" | IOSTANDARD = "LVCMOS33"; NET "leds8<6>" LOC = "R11" | IOSTANDARD = "LVCMOS33"; NET "leds8<7>" LOC = "T11" | IOSTANDARD = "LVCMOS33"; ## Switches NET "sw<0>" LOC = "T10" | IOSTANDARD = "LVCMOS33"; NET "sw<1>" LOC = "T9" | IOSTANDARD = "LVCMOS33"; NET "sw<2>" LOC = "V9" | IOSTANDARD = "LVCMOS33"; NET "sw<3>" LOC = "M8" | IOSTANDARD = "LVCMOS33"; NET "sw<4>" LOC = "N8" | IOSTANDARD = "LVCMOS33"; NET "sw<5>" LOC = "U8" | IOSTANDARD = "LVCMOS33"; NET "sw<6>" LOC = "V8" | IOSTANDARD = "LVCMOS33"; NET "sw<7>" LOC = "T5" | IOSTANDARD = "LVCMOS33"; NET "rst" LOC = "C9" | IOSTANDARD = "LVCMOS33"; ##JC NET "sda" LOC = "G1" | IOSTANDARD = LVTTL | PULLUP; NET "scl" LOC = "J7" | IOSTANDARD = LVTTL | PULLUP;
Bien sûr, chaque type de carte aura son fichier ucf. Mais cet exemple permettra une adaptation plus facile. Pour faciliter encore sa compréhension rappelons à l'aide de tableaux le câblage choisi.
Le câblage du PMod a été choisi avec la partie inférieure du connecteur en reliant la masse, ce que montre le tableau suivant :
i2c | -- | -- | -- | -- | SCL | SDA |
---|---|---|---|---|---|---|
Sparkfun Pro micro | -- | GND | -- | -- | 3 | 2 |
PMOD (Digilent) | 12 (Vcc) | 11 (GND) | 10 | 9 | 8 | 7 |
Évaluation grossière de la vitesse de transmission
[modifier | modifier le wikicode]Nous avons parlé d'une fréquence d'horloge du maître de 100 kHz mais qu'en est-il de la fréquence d'émission de de réception des 4 octets de notre cœur ?
La documentation officielle que l’on trouve avec le cœur fait état de :
- 8 octets à émettre au minimum pour écrire les 4 octets
- 7 octets bidirectionnels pour la réception
En admettant que l'envoi d'un octet est équivalent à l'envoi de 10 bits (à cause du start et stop), la fréquence maximale d'échange est donc :
Utiliser ce mode de dialogue en robotique mobile où l’on a besoin souvent d'informations sur les capteurs de 20 à 50 fois par seconde est donc envisageable.
A ce point nous avons à disposition deux modes de dialogues entre un processeur et un FPGA : le SPI et I2C. Ces deux modes sont très intéressants car pratiquement tous les microcontrôleurs ont à disposition des périphériques qui gèrent ces deux protocôles.
Nous allons maintenant complètement changer de processeur pour donner d'autres exemples.
Processeur MSP 430 et SPI
[modifier | modifier le wikicode]Le microcontrôleur MSP430 fait partie des architectures 16 bits. Il entre en concurrence avec les PIC24F. Sa particularité est sa gamme faible consommation.
Présentation du MSP430
[modifier | modifier le wikicode]Nous allons utiliser un MSP430 pour ce projet qui va consister à récupérer les valeurs données par le processeur dans le FPGA.
Le MSP430 est un processeur 16 bits présenté rapidement dans un chapitre de ce livre, qui est caractérisé par sa faible consommation. Il existe un kit appelé "Launchpad" à faible prix (environ 10 €). Son gros intérêt, à part son prix, est une tension de 3,3 V directement compatible avec le FPGA et un environnement de développement identique à l'Arduino.
Installer Energia sous Linux
[modifier | modifier le wikicode]Si vous téléchargez les binaires chez energia.nu, décompressez-les et votre environnement est en état de marche pour compiler.
Pour téléverser dans les processeurs, il faut ajouter dans un fichier 99-ti-launchpad.rules (dans /etc/udev/rules.d ) mettre :
# CC3200 LaunchPad SUBSYSTEM=="usb", ATTRS{idVendor}=="0451", ATTRS{idProduct}=="c32a", MODE="0660", GROUP="dialout", RUN+="/sbin/modprobe ftdi-sio" RUN+="/bin/sh -c '/bin/echo 0451 c32a > /sys/bus/usb-serial/drivers/ftdi_sio/new_id'" # MSP-EXP430G2 and MSP-EXP430FR5739 LaunchPad ATTRS{idVendor}=="0451", ATTRS{idProduct}=="f432", MODE="0660", GROUP="dialout" # MSP-EXP430F5529LP and MSP-EXP430FR5969 LaunchPad ATTRS{idVendor}=="1cbe", ATTR{idProduct}=="00fd", MODE="0660", GROUP="dialout" # EK-TM4C123GXL, EK-TM4C1294XL and EK-LM4F120XL LaunchPad ATTRS{idVendor}=="1cbe", ATTRS{idProduct}=="00fd", MODE="0660", GROUP="dialout"
A ce stade, seule la ligne correspondant à notre launchPad MSP-EXP430G2 est nécessaire. Mais nous utiliserons un peu plus loin, dans ce chapitre, le 32 bits ARM Cortex M4 TM4C123GXL. Autant faire d'une pierre deux coups.
Un redémarrage du service udev est alors nécessaire. La commande
sudo service udev restart
fera alors l'affaire.
La conversion analogique/numérique
[modifier | modifier le wikicode]Nous parlons de la conversion analogique/numérique ici car c’est une application naturelle à l'interfaçage d'un FPGA avec un microcontrôleur. Les convertisseurs Analogiques Numériques du MSP430 sont des convertisseurs sur 10 bits (comme pour les Arduinos) et fonctionnent jusqu'à 200 000 fois par seconde (Greater than 200-ksps maximum conversion rate). Ils peuvent passer à 12 bits pour certaines familles du MSP430.
Pour éviter à nos étudiants connaissant l'Arduino de se perdre dans un nouvel environnement, nous allons utiliser un environnement équivalent qui s’appelle Energia. Le langage correspondant est identique.
Comme pour l'Arduino, lire une valeur analogique est très simple. Il existe aussi une primitive appelée "analogRead".
Le protocole SPI
[modifier | modifier le wikicode]La programmation SPI, comme présentée dans le projet précédent, est facile. Nous ne reviendrons pas sur la présentation de ce protocole (voir projet correspondant avec l'Arduino de ce chapitre).
Pour faire fonctionner le protocole SPI il faut un maître et un esclave. Commençons par un maître réalisé avec le MSP430.
Le maître avec un MSP430
[modifier | modifier le wikicode]Voici un squelette de programme pour commencer à réfléchir :
/*************************
* Carte SD ----- Launchpad
* GND ----------- GND
* +3.3 ---------- Vcc
* CS ------------ P2.0 (8)
* MOSI ---------- P1.7 (15)
* SCK ----------- P1.5 (7)
* MISO ---------- P1.6 (14)
*
**************************/
#include <SPI.h>
const int chipSelectPin = 8; // P2.0
char dataToSend=0xA5;
void setup(){
pinMode(chipSelectPin, OUTPUT);
pinMode(15,OUTPUT);
pinMode(14,INPUT);
// start the SPI library:
SPI.begin();
SPI.setDataMode(SPI_MODE0);
SPI.setClockDivider(SPI_CLOCK_DIV128);
delay(100);
}
void loop() {
char recievedData;
digitalWrite(chipSelectPin, LOW);
recievedData=SPI.transfer(dataToSend); //Send register location
delay(10);
digitalWrite(chipSelectPin, HIGH);
delay(1000);
dataToSend++;
}
Nous avons passé beaucoup de temps avec ce programme trouvé sur Internet qui ne fonctionnait pas ! Il suffisait d'inverser les pinMode avec la librairie SPI comme ci-dessus.
Les commentaires en début de programme sont corrects. Nous l'affirmons ici car il y a plusieurs versions de LaunchPad. La notre est la LaunchPad avec un MSP430G2553 (version 1.5) dont le brochage peut être trouvé ICI.
Notre esclave va être présenté maintenant. Il s'agit d'une carte FPGA Nexys3 doté d'un spartan6.
Esclave SPI sur FPGA
[modifier | modifier le wikicode]La notion d'esclave SPI enfouie dans un FPGA a été abordée dans une précédente section.
Le câblage du PMod a été choisi encore comme :
SPI | -- | -- | MOSI | MISO | SCK | SS |
---|---|---|---|---|---|---|
PMOD | 12 (Vcc) | 11 (GND) | 10 | 9 | 8 | 7 |
MSP430 | -- | GND | P1.7 | P1.6 | P1.5 | P2.0 |
Projets associés
[modifier | modifier le wikicode]- PMod Processeur MSP430 et SPI pour la robotique mobile décrit un projet qui associe le Launchpad MSP430 et une Basys2 en vue d'une installation sur une base robotique mobile. Ce projet a été abandonné, en fait, mais son énoncé reste lisible.
CORDIC et MSP430
[modifier | modifier le wikicode]CORDIC est déjà évoqué dans :
- Chapitre 16 de ce livre
- CORDIC sur Arduino dans un autre projet
Nous ne reprendrons donc pas la théorie ici. Rappelons quand même que nous allons utiliser un format virgule fixe sur 16 bits de type Q3.13, ce qui veut dire que la partie décimale comporte 13 bits après la virgule. Si l’on se rapporte au décimal, cela fait 3 digits corrects après la virgule.
Version logicielle de CORDIC
[modifier | modifier le wikicode]Nous avons repris le programme Arduino et modifié un peu les types, particulièrement le type int. En effet, la taille du type int nécessite de se plonger dans la documentation du compilateur. Ne pas le faire nous expose à des erreurs comme cela nous est arrivé avec le type "long int" qui est sur 32 bits sur les AVRs mais 16 bits sur les PIC. Heureusement le compilateur du GNU nous permet de définir la taille des types de manière univoque et c’est ce que nous avons fait dans le programme ci-dessous.
void setup() {
Serial.begin(9600);
}
void loop() {
int16_t angle_Q3_13;
int16_t sinus, cosinus;
char chaine[10];
float angle;
Serial.println("Entrez un angle : ");
while (Serial.available()==0);
angle=Serial.parseFloat();
//conversion float vers Q3.13
angle_Q3_13 = float2HexQ3_13(angle);
cordic(angle_Q3_13,&sinus,&cosinus);
Serial.print("Sinus=");
HexQ3_13ToString(sinus,chaine);
Serial.println(chaine);
Serial.print("Cosinus=");
HexQ3_13ToString(cosinus,chaine);
Serial.println(chaine);
// on vide le tampon au cas où
while (Serial.available()!=0) Serial.read();
}
//************************************************************************
// function HexQ3_13ToFloat_MSP430()
// purpose: transformation of a 16-bit Q3.13 number into 32-bit float number
// arguments:
// corresponding Q3.13 number
// return: 32-bit float
// note: This function works on Arduino Mega 2560
//************************************************************************
float HexQ3_13ToFloat_MSP430(int16_t val){
union {
float f_temp; //taille : 4 octets
int32_t li_temp; //taille : 4 octets
} u;
int32_t i_temp;
unsigned char exposant=129;//,*p;
signed char i;
if (val < 0) i_temp = -val; else i_temp = val;
for (i=15;i>=0;i--) {
if (i_temp & (1<<i)) {
// on efface le '1' trouvé :
i_temp= i_temp & ~(1<<i);
break;// on sort de la boucle
}
exposant--;
}
u.li_temp = exposant;
u.li_temp <<=23;
u.li_temp = u.li_temp|(i_temp << (23-i));
if (val < 0)
u.f_temp = -u.f_temp;//-u.f_temp;
return u.f_temp;
}
//************************************************************************
// function float2HexQ3_13()
// purpose: transformation of a 32-bit float number into Q3.13
// arguments:
// corresponding float number and the returned string
// return: integer in Q3.13 format
// note:
//************************************************************************
int16_t float2HexQ3_13(float val){ //conversion float vers Q3.13
int16_t temp;
char i;
float f_temp;
if (val < 0) f_temp = -val; else f_temp = val;
temp = ((int) floor(f_temp)<<13);
f_temp = f_temp - floor(f_temp);
for (i=0;i<13;i++) {
temp|=((int)floor(2*f_temp)<<(12-i));
f_temp = 2*f_temp - floor(2*f_temp);
}
if (val < 0) return -temp; else return temp;
}
//************************************************************************
// function float2HexQ3_13_MSP430()
// purpose: transformation of a 32-bit float number into Q3.13
// arguments:
// corresponding float number and the returned string
// return: integer in Q3.13 format
// note: idem to float2HexQ3_13 but without float library
// utilise 500 octets de moins que la précédente
//************************************************************************
int16_t float2HexQ3_13_MSP430(float val){ //conversion float vers Q3.13
union {
float f_temp; //taille : 4 octets
int32_t li_temp; //taille : 4 octets
} u,v;
unsigned char exposant;
int32_t mantisse;
int16_t result=0;
u.f_temp = val;
if (val < 0) u.f_temp = - u.f_temp;
v = u;
// recupération de l'exposant
v.li_temp >>= 23;
exposant = v.li_temp;
// recuperation mantisse
if (exposant < 129) {
mantisse = (u.li_temp & 0X007FFFFF);
// mise à 1 du bit manquant
result |= (1 << (exposant-114)); // (15-129+exposant)); semble OK
mantisse >>= (137-exposant); //(22-(14-129+exposant));
result |= mantisse;
if (val < 0) return -result;
else return result;
} else {
//**** "Erreur conversion"*****
//Serial.println("Erreur conversion");
return 0;
}
}
//************************************************************************
// function HexQ3_13ToString()
// purpose: transformation of a Q3.13 number into string
// arguments:
// corresponding Q3.13 number and the returned string
// return:
// note: only four decimal digit of too lengthy numbers are calculated !!
//************************************************************************
void HexQ3_13ToString(int16_t valQ3_13,char str[]){ // pi/4=0,785 est mal converti ?
uint16_t valQ3_13b,valQ3_13c;
char digit;
if (valQ3_13 < 0) { // eviter problèmes de signe !!!
str[0] = '-';
valQ3_13 = -valQ3_13;
} else
str[0] = '+';
valQ3_13c = valQ3_13;
digit = valQ3_13c >> 13;
str[1]= digit + '0';
str[2]= '.';
valQ3_13c &= 0x1FFF; // on retire les 3 bits de poids fort
//Serial.print("0x");
valQ3_13c = valQ3_13c * 5; // *5 pour tenir dans 16 bits
//valQ3_13 = valQ3_13 + (valQ3_13<<2);
valQ3_13b = valQ3_13c;
valQ3_13b >>= 12; // on ne garde que les 4 bits de poids fort
str[3] = valQ3_13b +'0';
valQ3_13c &= 0x0FFF; // on retire les 4 bits de poids fort
valQ3_13c = valQ3_13c * 10; //enfin le *10
valQ3_13b = valQ3_13c;
valQ3_13b >>= 12; // on ne garde que les 4 bits de poids fort0.789
str[4] = valQ3_13b +'0';
valQ3_13c &= 0x0FFF; // on retire les 4 bits de poids fort
valQ3_13c = valQ3_13c * 10;
valQ3_13b = valQ3_13c;
valQ3_13b >>= 12; // on ne garde que les 4 bits de poids fort
str[5] = valQ3_13b +'0';
valQ3_13c &= 0x0FFF; // on retire les 4 bits de poids fort
valQ3_13c = valQ3_13c * 10;
valQ3_13b = valQ3_13c;
valQ3_13b >>= 12; // on ne garde que les 4 bits de poids fort
str[6] = valQ3_13b +'0';
str[7]=0;
}
//************************************************************************
// function cordic()
// purpose: calculation of sine and cosine of an Q3.13 angle
// arguments:
// corresponding Q3.13 number and the returned Q3.13
// return:
// note:
//************************************************************************
void cordic(int16_t beta,int16_t *sinus,int16_t *cosinus) {
unsigned char i;
int16_t K = 0x136F; //=0.6073 en Q3.13
int16_t atantb[14]={0x1921,0xED6,0x7D6,0x3FA,0x1FF,0xFF,0x7F,0x3F,0x1F,0xF,0x7,0x3,0x2,0x1};
int16_t x_Nouveau;
*cosinus = K;
*sinus=0;
// itération CORDIC proprement dite :
for(i = 0; i < 13; i++) {
// Si beta<0 rotation dans le sens trigo
if(beta < 0) {
x_Nouveau = *cosinus + (*sinus>>i);
*sinus -= *cosinus>>i;
beta += atantb[i];
}
// sinon dans l'autre sens
else {
x_Nouveau = *cosinus - (*sinus>>i);
*sinus += (*cosinus>>i);
beta -= atantb[i];
}
*cosinus = x_Nouveau;
}
}
Remplacer
//conversion float vers Q3.13
angle_Q3_13 = float2HexQ3_13(angle);
qui se trouve dans loop() par
//conversion float vers Q3.13
angle_Q3_13 = float2HexQ3_13_MSP430(angle);
Gardez à l'esprit que ce qui est publié ci-dessus est fonctionnel pour le MSP430. Vous ne devrez tenir compte de la remarque ci-dessous que si vous voulez l’utiliser dans l'environnement Arduino (ou avr-gcc en général).
Le sous-programme "HexQ3_13ToString" que nous utilisions depuis deux ans avec les AVR, nous a montré quelques faiblesses avec le compilateur C du MSP430 ! Il a donc été remanié et nous avons l'intention de tester cette nouvelle version dans les AVRs, mais cela n'a pas encore été fait !
La raison du dysfonctionnement est le décalage vers la droite. On rappelle qu'en C un tel décalage ne fait pas toujours la même chose pour les nombres signés ou pas signés. Si on utilise des nombres non signés, il rentrera systématiquement '0'. Si l’on utilise les nombres signés il rentrera un '0' si les nombres sont positifs et un '1' si les nombres sont négatifs.
Cœur FPGA CORDIC interfacé au MSP430 par une liaison SPI
[modifier | modifier le wikicode]Nous allons maintenant nous intéresser à l'interfaçage d'un cœur matériel CORDIC dans un FPGA avec un processeur externe de type MSP430. Nous avons choisi pour cela le SPI plutôt qu'un autre protocole car il nous semble être le plus rapide (horloge à 8MHz =fréquence quartz/2).
/*************************
* Carte SD ----- Launchpad
* GND ----------- GND
* +3.3 ---------- Vcc
* CS ------------ P2.0 (8)
* MOSI ---------- P1.7 (15)
* SCK ----------- P1.5 (7)
* MISO ---------- P1.6 (14)
*
**************************/
#include <SPI.h>
//#define DEBUG
const int chipSelectPin = 8; // P2.0
char dataToSend=0xA5;
void setup(){
pinMode(chipSelectPin, OUTPUT);
pinMode(15,OUTPUT);
pinMode(14,INPUT);
// start the SPI library:
SPI.begin();
SPI.setDataMode(SPI_MODE0);
SPI.setClockDivider(SPI_CLOCK_DIV8);
delay(100);
Serial.begin(9600);
//angle=float2HexQ3_13(0.785);
//HexQ3_13ToString(angle,result);
//Serial.println(result);
delay(5000);
}
void loop() {
int16_t sinus,cosinus;
static int16_t angle_Q3_13=0x0800;
float f_angle=0.0;
int16_t sinusLow,sinusHigh,angleLow,angleHigh;
char result[8];
Serial.println("Entrez un angle : ");
while (Serial.available()==0);
f_angle = Serial.parseFloat();
//conversion float vers Q3.13
angle_Q3_13 = float2HexQ3_13(f_angle);
HexQ3_13ToString(angle_Q3_13,result);
Serial.print("Angle = ");
Serial.println(result);
angleLow = angle_Q3_13;
Serial.print("Low=0x");
Serial.print(angleLow,HEX);
angleHigh = (angle_Q3_13 >> 8);
Serial.print(" High=0x");
Serial.println(angleHigh,HEX);
// pret pour SPI
digitalWrite(chipSelectPin, LOW);
// delay(1);
sinusHigh=SPI.transfer(angleHigh); //Send register location
// delay(1);
sinusLow=SPI.transfer(angleLow); //Send register location
// delay(1);
digitalWrite(chipSelectPin, HIGH);
// une deuxième fois pour récupérer résultat après un peu d'attente
// delay(1);
digitalWrite(chipSelectPin, LOW);
// delay(1);
sinusHigh=SPI.transfer(angleHigh); //Send register location
//delay(1);
sinusLow=SPI.transfer(angleLow); //Send register location
digitalWrite(chipSelectPin, HIGH);
Serial.print("SINUS : Low=0x");
Serial.print(sinusLow,HEX);
Serial.print(" High=0x");
Serial.println(sinusHigh,HEX);
sinus = (sinusHigh<<8) + sinusLow;
Serial.print("Sinus=");
HexQ3_13ToString(sinus,result);
Serial.println(result);
// cordic en local
cordic(angle_Q3_13,&sinus,&cosinus);
Serial.print("CORDIC local : Sinus=");
HexQ3_13ToString(sinus,result);
Serial.println(result);
delay(2000);
}
La partie matérielle est aussi donnée (sans le cœur SPI) que l’on peut trouver sur Internet.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.std_logic_unsigned.all;
entity msp430_fpgaCORDIC is port (
clk_100MHz : in std_logic; -- Carte Nexys 3 avec horloge à 100 MHz
MSP430rxd : in std_logic;
I_Rx : in std_logic;
-- Port SPI
mosi : in std_logic;
miso : out std_logic;
-- mosi_copy : out std_logic;
ss : in std_logic;
sck : in std_logic;
MSP430txd : out std_logic;
Q_Tx : out std_logic
);
end msp430_fpgaCORDIC;
architecture arch of msp430_fpgaCORDIC is
component spi_slave is
Generic (
N : positive := 32; -- 32bit serial word length is default
CPOL : std_logic := '0'; -- SPI mode selection (mode 0 default)
CPHA : std_logic := '0'; -- CPOL = clock polarity, CPHA = clock phase.
PREFETCH : positive := 3); -- prefetch lookahead cycles
Port (
clk_i : in std_logic := 'X'; -- internal interface clock (clocks di/do registers)
spi_ssel_i : in std_logic := 'X'; -- spi bus slave select line
spi_sck_i : in std_logic := 'X'; -- spi bus sck clock (clocks the shift register core)
spi_mosi_i : in std_logic := 'X'; -- spi bus mosi input
spi_miso_o : out std_logic := 'X'; -- spi bus spi_miso_o output
di_req_o : out std_logic; -- preload lookahead data request line
di_i : in std_logic_vector (N-1 downto 0) := (others => 'X'); -- parallel load data in (clocked in on rising edge of clk_i)
wren_i : in std_logic := 'X'; -- user data write enable
wr_ack_o : out std_logic; -- write acknowledge
do_valid_o : out std_logic; -- do_o data valid strobe, valid during one clk_i rising edge.
do_o : out std_logic_vector (N-1 downto 0);
-- parallel output (clocked out on falling clk_i)
--- debug ports: can be removed for the application circuit ---
do_transfer_o : out std_logic; -- debug: internal transfer driver
wren_o : out std_logic; -- debug: internal state of the wren_i pulse stretcher
rx_bit_next_o : out std_logic; -- debug: internal rx bit
state_dbg_o : out std_logic_vector (3 downto 0); -- debug: internal state register
sh_reg_dbg_o : out std_logic_vector (N-1 downto 0) -- debug: internal shift register
);
end component;
component sc_corproc is
port(
clk : in std_logic;
ena : in std_logic;
Ain : in std_logic_vector(15 downto 0);
done : out std_logic;
sin : out std_logic_vector(15 downto 0);
cos : out std_logic_vector(15 downto 0));
end component sc_corproc;
signal s_Angle,s_sin,s_cos : std_logic_vector(15 downto 0);
signal s_done : std_logic;
begin
MSP430txd <= I_rx;
Q_tx <= MSP430rxd;
ic1 : spi_slave generic map (N => 16)
port map ( clk_i => clk_100MHz,
do_o => s_Angle,
di_i => s_sin,
spi_ssel_i => ss,
spi_sck_i => sck,
spi_mosi_i => mosi,
spi_miso_o => miso,
wren_i => not ss--s_wren_i
);
ic2 : sc_corproc port map (
clk => clk_100MHz,
ena => not ss,
Ain => s_Angle,
done => s_done,
sin => s_sin,
cos => s_cos
);
end arch;
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;
entity sc_corproc is
port(
clk : in std_logic;
ena : in std_logic;
Ain : in std_logic_vector(15 downto 0);
done : out std_logic;
sin : out std_logic_vector(15 downto 0);
cos : out std_logic_vector(15 downto 0));
end entity sc_corproc;
ARCHITECTURE rtl OF sc_corproc IS
-- TYPE signed_array IS signed(15 DOWNTO 0);
SIGNAL x_array : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_array : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL x_array_1 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array_1 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_array_1 : std_logic_vector(15 DOWNTO 0);
SIGNAL x_array_2 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array_2 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_array_2 : std_logic_vector(15 DOWNTO 0);
SIGNAL x_array_3 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array_3 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_array_3 : std_logic_vector(15 DOWNTO 0);
SIGNAL x_array_4 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array_4 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_array_4 : std_logic_vector(15 DOWNTO 0);
SIGNAL x_array_5 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array_5 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_array_5 : std_logic_vector(15 DOWNTO 0);
SIGNAL x_array_6 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array_6 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_array_6 : std_logic_vector(15 DOWNTO 0);
SIGNAL x_array_7 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array_7 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_array_7 : std_logic_vector(15 DOWNTO 0);
SIGNAL x_array_8 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array_8 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_array_8 : std_logic_vector(15 DOWNTO 0);
SIGNAL x_array_9 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array_9 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_array_9 : std_logic_vector(15 DOWNTO 0);
SIGNAL x_array_10 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array_10 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_array_10 : std_logic_vector(15 DOWNTO 0);
SIGNAL x_array_11 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array_11 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_array_11 : std_logic_vector(15 DOWNTO 0);
SIGNAL x_array_12 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array_12 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_array_12 : std_logic_vector(15 DOWNTO 0);
SIGNAL x_array_13 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL y_array_13 : std_logic_vector(15 DOWNTO 0);--signed_array;-- := (OTHERS =>'0');
SIGNAL z_ip : std_logic_vector(15 DOWNTO 0);
SIGNAL x_ip : std_logic_vector(15 DOWNTO 0);
SIGNAL y_ip : std_logic_vector(15 DOWNTO 0);
-- compteur pour signal done
SIGNAL cmpt :std_logic_vector(3 DOWNTO 0);
BEGIN
-- compteur de gestion du signal done
process(clk) begin
if rising_edge(clk) then
if ena ='1' then
if cmpt < 14 then
cmpt <= cmpt + 1;
else
cmpt <= x"E";
end if;
else
cmpt <=x"0";
end if;
end if;
end process;
-- gestion done CORDIC
process(cmpt) begin
if cmpt = x"E" then
done <= '1';
else
done <= '0';
end if;
end process;
--CORDIC proprement dit
PROCESS(clk)
BEGIN
IF rising_edge(clk) THEN
IF ena = '0' THEN
x_ip <= x"136F"; -- = 0.6073 en format Q3.13 ;
z_ip <= (OTHERS => '0');
y_ip <= (OTHERS => '0');
ELSE
IF Ain(15) = '1' THEN
x_array <= (x_ip) + (y_ip);
y_array <= (y_ip) - (x_ip);
-- x_array <= x"136F";
-- y_array <= 0 - x"136F";
z_array <= (Ain) + x"1921";--tan_array(0);
ELSE
x_array <= x"136F";
y_array <= x"136F";
z_array <= (Ain) - x"1921";--tan_array(0);
-- z_array <= x"2181"- x"1921";--tan_array(0);
END IF;
IF z_array(15) = '1' THEN
x_array_1 <= x_array + (y_array(15) & y_array(15 downto 1));
y_array_1 <= y_array - (x_array(15) & x_array(15 downto 1));
z_array_1 <= z_array + x"0ED6";
ELSE
x_array_1 <= x_array - (y_array(15) & y_array(15 downto 1));
y_array_1 <= y_array + (x_array(15) & x_array(15 downto 1));
z_array_1 <= z_array - x"0ED6";
END IF;
IF z_array_1(15) = '1' THEN
x_array_2 <= x_array_1 + (y_array_1(15) & y_array_1(15) & y_array_1(15 downto 2));
y_array_2 <= y_array_1 - (x_array_1(15) & x_array_1(15) & x_array_1(15 downto 2));
z_array_2 <= z_array_1 + x"07D6";
ELSE
x_array_2 <= x_array_1 - (y_array_1(15) & y_array_1(15) & y_array_1(15 downto 2));
y_array_2 <= y_array_1 + (x_array_1(15) & x_array_1(15) & x_array_1(15 downto 2));
z_array_2 <= z_array_1 - x"07D6";
END IF;
IF z_array_2(15) = '1' THEN
x_array_3 <= x_array_2 + (y_array_2(15) & y_array_2(15) & y_array_2(15) & y_array_2(15 downto 3));
y_array_3 <= y_array_2 - (x_array_2(15) & x_array_2(15) & x_array_2(15) & x_array_2(15 downto 3));
z_array_3 <= z_array_2 + x"03FA";
ELSE
x_array_3 <= x_array_2 - (y_array_2(15) & y_array_2(15) & y_array_2(15) & y_array_2(15 downto 3));
y_array_3 <= y_array_2 + (x_array_2(15) & x_array_2(15) & x_array_2(15) & x_array_2(15 downto 3));
z_array_3 <= z_array_2 - x"03FA";
END IF;
IF z_array_3(15) = '1' THEN
x_array_4 <= x_array_3 + (y_array_3(15) & y_array_3(15) & y_array_3(15) &
y_array_3(15) & y_array_3(15 downto 4));
y_array_4 <= y_array_3 - (x_array_3(15) & x_array_3(15) & x_array_3(15) &
x_array_3(15) & x_array_3(15 downto 4));
z_array_4 <= z_array_3 + x"01FF";
ELSE
x_array_4 <= x_array_3 - (y_array_3(15) & y_array_3(15) & y_array_3(15) &
y_array_3(15) & y_array_3(15 downto 4));
y_array_4 <= y_array_3 + (x_array_3(15) & x_array_3(15) & x_array_3(15) &
x_array_3(15) & x_array_3(15 downto 4));
z_array_4 <= z_array_3 - x"01FF";
END IF;
IF z_array_4(15) = '1' THEN
x_array_5 <= x_array_4 + (y_array_4(15) & y_array_4(15) & y_array_4(15) &
y_array_4(15) & y_array_4(15) & y_array_4(15 downto 5));
y_array_5 <= y_array_4 - (x_array_4(15) & x_array_4(15) & x_array_4(15) &
x_array_4(15) & x_array_4(15) & x_array_4(15 downto 5));
z_array_5 <= z_array_4 + x"00FF";
ELSE
x_array_5 <= x_array_4 - (y_array_4(15) & y_array_4(15) & y_array_4(15) &
y_array_4(15) & y_array_4(15) & y_array_4(15 downto 5));
y_array_5 <= y_array_4 + (x_array_4(15) & x_array_4(15) & x_array_4(15) &
x_array_4(15) & x_array_4(15) & x_array_4(15 downto 5));
z_array_5 <= z_array_4 - x"00FF";
END IF;
IF z_array_5(15) = '1' THEN
x_array_6 <= x_array_5 + (y_array_5(15) & y_array_5(15) & y_array_5(15) &
--y_array_5(15) & y_array_5(15) & y_array_5(15) & y_array_5(15) & y_array_5(15 downto 6));
y_array_5(15) & y_array_5(15) & y_array_5(15) & y_array_5(15 downto 6));
y_array_6 <= y_array_5 - (x_array_5(15) & x_array_5(15) & x_array_5(15) &
--x_array_5(15) & x_array_5(15) & x_array_5(15) & x_array_5(15) & x_array_5(15 downto 6));
x_array_5(15) & x_array_5(15) & x_array_5(15) & x_array_5(15 downto 6));
z_array_6 <= z_array_5 + x"007F";
ELSE
x_array_6 <= x_array_5 - (y_array_5(15) & y_array_5(15) & y_array_5(15) &
y_array_5(15) & y_array_5(15) & y_array_5(15) & y_array_5(15 downto 6));
y_array_6 <= y_array_5 + (x_array_5(15) & x_array_5(15) & x_array_5(15) &
x_array_5(15) & x_array_5(15) & x_array_5(15) & x_array_5(15 downto 6));
z_array_6 <= z_array_5 - x"007F";
END IF;
IF z_array_6(15) = '1' THEN
x_array_7 <= x_array_6 + (y_array_6(15) & y_array_6(15) & y_array_6(15) &
y_array_6(15) & y_array_6(15) & y_array_6(15) & y_array_6(15) &
y_array_6(15 downto 7));
y_array_7 <= y_array_6 - (x_array_6(15) & x_array_6(15) & x_array_6(15) &
x_array_6(15) & x_array_6(15) & x_array_6(15) & x_array_6(15) &
x_array_6(15 downto 7));
z_array_7 <= z_array_6 + x"003F";
ELSE
x_array_7 <= x_array_6 - (y_array_6(15) & y_array_6(15) & y_array_6(15) &
y_array_6(15) & y_array_6(15) & y_array_6(15) & y_array_6(15) &
y_array_6(15 downto 7));
y_array_7 <= y_array_6 + (x_array_6(15) & x_array_6(15) & x_array_6(15) &
x_array_6(15) & x_array_6(15) & x_array_6(15) & x_array_6(15) &
x_array_6(15 downto 7));
z_array_7 <= z_array_6 - x"003F";
END IF;
IF z_array_7(15) = '1' THEN
x_array_8 <= x_array_7 + (y_array_7(15) & y_array_7(15) & y_array_7(15) &
y_array_7(15) & y_array_7(15) & y_array_7(15) & y_array_7(15) & y_array_7(15) &
y_array_7(15 downto 8));
y_array_8 <= y_array_7 - (x_array_7(15) & x_array_7(15) & x_array_7(15) &
x_array_7(15) & x_array_7(15) & x_array_7(15) & x_array_7(15) & x_array_7(15) &
x_array_7(15 downto 8));
z_array_8 <= z_array_7 + x"001F";
ELSE
x_array_8 <= x_array_7 - (y_array_7(15) & y_array_7(15) & y_array_7(15) &
y_array_7(15) & y_array_7(15) & y_array_7(15) & y_array_7(15) & y_array_7(15) &
y_array_7(15 downto 8));
y_array_8 <= y_array_7 + (x_array_7(15) & x_array_7(15) & x_array_7(15) &
x_array_7(15) & x_array_7(15) & x_array_7(15) & x_array_7(15) & x_array_7(15) &
x_array_7(15 downto 8));
z_array_8 <= z_array_7 - x"001F";
END IF;
IF z_array_8(15) = '1' THEN
x_array_9 <= x_array_8 + (y_array_8(15) & y_array_8(15) & y_array_8(15) &
y_array_8(15) & y_array_8(15) & y_array_8(15) & y_array_8(15) & y_array_8(15) &
y_array_8(15) & y_array_8(15 downto 9));
y_array_9 <= y_array_8 - (x_array_8(15) & x_array_8(15) & x_array_8(15) &
x_array_8(15) & x_array_8(15) & x_array_8(15) & x_array_8(15) & x_array_8(15) &
x_array_8(15) & x_array_8(15 downto 9));
z_array_9 <= z_array_8 + x"000F";
ELSE
x_array_9 <= x_array_8 - (y_array_8(15) & y_array_8(15) & y_array_8(15) &
y_array_8(15) & y_array_8(15) & y_array_8(15) & y_array_8(15) & y_array_8(15) &
y_array_8(15) & y_array_8(15 downto 9));
y_array_9 <= y_array_8 + (x_array_8(15) & x_array_8(15) & x_array_8(15) &
x_array_8(15) & x_array_8(15) & x_array_8(15) & x_array_8(15) & x_array_8(15) &
x_array_8(15) & x_array_8(15 downto 9));
z_array_9 <= z_array_8 - x"000F";
END IF;
IF z_array_9(15) = '1' THEN
x_array_10 <= x_array_9 + (y_array_9(15) & y_array_9(15) & y_array_9(15) &
y_array_9(15) & y_array_9(15) & y_array_9(15) & y_array_9(15) & y_array_9(15) &
y_array_9(15) & y_array_9(15) & y_array_9(15 downto 10));
y_array_10 <= y_array_9 - (x_array_9(15) & x_array_9(15) & x_array_9(15) &
x_array_9(15) & x_array_9(15) & x_array_9(15) & x_array_9(15) & x_array_9(15) &
x_array_9(15) & x_array_9(15) & x_array_9(15 downto 10));
z_array_10 <= z_array_9 + x"0007";
ELSE
x_array_10 <= x_array_9 - (y_array_9(15) & y_array_9(15) & y_array_9(15) &
y_array_9(15) & y_array_9(15) & y_array_9(15) & y_array_9(15) & y_array_9(15) &
y_array_9(15) & y_array_9(15) & y_array_9(15 downto 10));
y_array_10 <= y_array_9 + (x_array_9(15) & x_array_9(15) & x_array_9(15) &
x_array_9(15) & x_array_9(15) & x_array_9(15) & x_array_9(15) & x_array_9(15) &
x_array_9(15) & x_array_9(15) & x_array_9(15 downto 10));
z_array_10 <= z_array_9 - x"0007";
END IF;
IF z_array_10(15) = '1' THEN
x_array_11 <= x_array_10 + (y_array_10(15) & y_array_10(15) & y_array_10(15) &
y_array_10(15) & y_array_10(15) & y_array_10(15) & y_array_10(15) & y_array_10(15) &
y_array_10(15) & y_array_10(15) & y_array_10(15) & y_array_10(15 downto 11));
y_array_11 <= y_array_10 - (x_array_10(15) & x_array_10(15) & x_array_10(15) &
x_array_10(15) & x_array_10(15) & x_array_10(15) & x_array_10(15) & x_array_10(15) &
x_array_10(15) & x_array_10(15) & x_array_10(15) & x_array_10(15 downto 11));
z_array_11 <= z_array_10 + x"0003";
ELSE
x_array_11 <= x_array_10 - (y_array_10(15) & y_array_10(15) & y_array_10(15) &
y_array_10(15) & y_array_10(15) & y_array_10(15) & y_array_10(15) & y_array_10(15) &
y_array_10(15) & y_array_10(15) & y_array_10(15) & y_array_10(15 downto 11));
y_array_11 <= y_array_10 + (x_array_10(15) & x_array_10(15) & x_array_10(15) &
x_array_10(15) & x_array_10(15) & x_array_10(15) & x_array_10(15) & x_array_10(15) &
x_array_10(15) & x_array_10(15) & x_array_10(15) & x_array_10(15 downto 11));
z_array_11 <= z_array_10 - x"0003";
END IF;
IF z_array_11(15) = '1' THEN
x_array_12 <= x_array_11 + (y_array_11(15) & y_array_11(15) & y_array_11(15) & y_array_11(15) &
y_array_11(15) & y_array_11(15) & y_array_11(15) & y_array_11(15) & y_array_11(15) & y_array_11(15) &
y_array_11(15) & y_array_11(15) & y_array_11(15 downto 12));
y_array_12 <= y_array_11 - (x_array_11(15) & x_array_11(15) & x_array_11(15) &
x_array_11(15) & x_array_11(15) & x_array_11(15) & x_array_11(15) & x_array_11(15) & x_array_11(15) &
x_array_11(15) & x_array_11(15) & x_array_11(15 downto 12));
z_array_12 <= z_array_11 + x"0001";
ELSE
x_array_12 <= x_array_11 - (y_array_11(15) & y_array_11(15) & y_array_11(15) & y_array_11(15) &
y_array_11(15) & y_array_11(15) & y_array_11(15) & y_array_11(15) & y_array_11(15) & y_array_11(15) &
y_array_11(15) & y_array_11(15) & y_array_11(15 downto 12));
y_array_12 <= y_array_11 + (x_array_11(15) & x_array_11(15) & x_array_11(15) & x_array_11(15) &
x_array_11(15) & x_array_11(15) & x_array_11(15) & x_array_11(15) & x_array_11(15) & x_array_11(15) &
x_array_11(15) & x_array_11(15) & x_array_11(15 downto 12));
z_array_12 <= z_array_11 - x"0001";
END IF;
IF z_array_12(15) = '1' THEN
x_array_13 <= x_array_12 + (y_array_12(15) & y_array_12(15) & y_array_12(15) & y_array_12(15) &
y_array_12(15) & y_array_12(15) & y_array_12(15) & y_array_12(15) & y_array_12(15) & y_array_12(15) &
y_array_12(15) & y_array_12(15) & y_array_12(15) & y_array_12(15 downto 13));
y_array_13 <= y_array_12 - (x_array_12(15) & x_array_12(15) & x_array_12(15) & x_array_12(15) &
x_array_12(15) & x_array_12(15) & x_array_12(15) & x_array_12(15) & x_array_12(15) &
x_array_12(15) & x_array_12(15) & x_array_12(15) & x_array_12(15) & x_array_12(15 downto 13));
-- z_array_13 <= z_array_12 + x"0000";
ELSE
x_array_13 <= x_array_12 - (y_array_12(15) & y_array_12(15) & y_array_12(15) & y_array_12(15) &
y_array_12(15) & y_array_12(15) & y_array_12(15) & y_array_12(15) & y_array_12(15) & y_array_12(15) &
y_array_12(15) & y_array_12(15) & y_array_12(15) & y_array_12(15 downto 13));
y_array_13 <= y_array_12 + (x_array_12(15) & x_array_12(15) & x_array_12(15) & x_array_12(15) &
x_array_12(15) & x_array_12(15) & x_array_12(15) & x_array_12(15) & x_array_12(15) & x_array_12(15) &
x_array_12(15) & x_array_12(15) & x_array_12(15) & x_array_12(15 downto 13));
-- z_array_13 <= z_array_12 - x"0000";
END IF;
END IF;
END IF;
END PROCESS;
cos <= x_array_13;
sin <= y_array_13;
END rtl;
La version présentée est fonctionnelle mais peut être largement améliorée puisque seul le sinus est retourné au MSP 430 par la liaison SPI. Nous laissons le soin aux lecteurs de perfectionner cet ensemble puisque, le but ici, est simplement de vous montrer comment cette liaison est possible.
Certain se demandent pourquoi nous avons utilisé le FPGA pour gérer la liaison série ? La raison est que nous avons beaucoup de mal à faire transiter par la même liaison, la programmation d'une part, et une liaison série d’autre part, et ceci sous Linux comme sous Windows XP. Il semble tout simplement que la liaison USB ne puisse pas servir aux deux, ce qui n’est pas le cas pour les platines 32 bits de chez Texas. En lisant plus attentivement la documentation, nous avons compris que la msp430 launchpad utilisée, n'était pas conçue pour de la liaison série à travers l'USB.
Il y a d'autres moyens de communications à explorer avec le MSP430. Par exemple, le MSP430 contient une gestion DMA certes limitée, mais nous n'avons pas suffisamment détaillé sa documentation pour voir s'il était possible de l’utiliser pour notre problème. Ce problème ne sera probablement jamais exploré ici car le msp430 launchpad est remplacé progressivement par le msp432 launchpad étudié un peu plus loin.
Nous allons maintenant entrer dans le domaine des 32 bits. Ce qui a été vu précédemment sur le SPI et l'i2c reste d'actualité, mais nous allons explorer d'autres modes de transferts.
Utilisation du TM4C123GH6PM (Tiva C LaunchPad)
[modifier | modifier le wikicode]Nous entrons maintenant dans le domaine des processeurs 32 bits avec un TM4C123GH6PM qui est un ARM Cortex 4. Sa documentation officielle se trouve facilement sur Internet. Son choix est lié à l’existence d'une platine d'évaluation ((en)Tiva-C LaunchPad) avec :
- un prix de moins de 17 € (13,23 € HT chez Farnel)
- un environnement de développement du type Arduino appelé Energia
- une fixation plus aisée que pour le LaunchPad MSP430 (point de vue qui reste discutable)
A l’heure où nous écrivons ces lignes (octobre 2014) l'environnement Arduino ne supporte le DUE (32 bits) qu'en version beta et tout ceci pour un prix approchant les 40 €. Le seul concurrent que nous voyons au Tiva-C LaunchPad est la Teensy 3.1. Elle se programme directement dans l'environnement Arduino (avec un Plugin ajouté) et coûte moins de 20 €.
Quatre années plus tard (2018) l'environnement Arduino supporte en plus le Blue Pill STM32 qui est une carte 32 bits que l'on trouvait aux alentours de 1,1 € à l'époque. Aujourd'hui (2020) le prix est plutôt autour de 1,5 €. Il existe une version un peu plus chère mais avec 128/256 ko de Flash (contre 64ko pour le Blue Pill).
En 2019 (et peut être probablement avant) on pouvait trouver une autre famille de 32 bits, l'ESP32. Il s'agit d'un double cœur d'un processeur 32 bits qui n'est pas un ARM 32 bits. Il est aussi programmable à partir de l'environnement Arduino pour un prix aux alentours de 3 €.
En 2020 un nouveau processeur fait son apparition. Il s'agit du risc v 32 bits avec des cartes autour de 4 €. Une autre version double cœur 64 bits autour de 20 €, est disponible, le tout encore programmable avec l'environnement Arduino.
Même si nous abordons le domaine des 32 bits, nous ne pourrons en aucun cas concurrencer les FPGA de type (en) Zynq de chez Xilinx en mettant un processeur externe. Si la vitesse est votre problème, prenez un Zynq. Si la simplicité est votre problème, envisagez un processeur externe.
La documentation de la platine que nous allons utiliser se trouve ICI sur le site Energia. Cela laisserait penser que Texas Instrument apporte un peu d'aide au développement d'Energia. Robert Wessels qui développe Energia depuis 2012 semble faire partie du personnel de Texas Instrument. Côté concurrence, vu la vitesse de développement de l'IDE Arduino ce ne serait pas le cas d'Atmel ? Nous parlons bien au conditionnel car nous n'avons aucune information officielle sur le sujet. Nous croyons savoir qu'Atmel serait actif pour le développement du compilateur avr-gcc. Chacun sa stratégie. Laissons donc ces stratégies commerciales aux professionnels pour entrer dans ce que nous connaissons le mieux, la technique.
L'exécution du compilateur C avec Energia avec un environnement Linux 64 bits nécessite quand même des librairies 32 bits. Nous avons fini par trouver un problème équivalent dans le Forum Arduino. Ainsi, l'exécution des trois lignes présentées, sous Ubuntu a résolu nos problème :
sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install libc6:i386
Nous allons commencer par étudier notre vieil ami, le protocole SPI.
Le protocole SPI
[modifier | modifier le wikicode]La programmation SPI est la même que pour le MSP430 puisque l'environnement de développement est identique. Nous donnons quand même le programme puisque le brochage change. Faites un effort avec la documentation de la platine pour constater la bonne utilisation du SPI.
/*************************
* Carte SD ----- Launchpad
* TM4C123G (ARM)
* GND ----------- GND
* +3.3 ---------- Vcc
* CS ------------ PB5 (PB_5)
* MOSI ---------- PB7 (PB_7)
* SCK ----------- PB4 (PB_4)
* MISO ---------- PB6 (PB_6)
*
**************************/
#include <SPI.h>
const int chipSelectPin = PB_5; // P2.0
int dataToSend=0xAA;
void setup(){
char i;
pinMode(chipSelectPin, OUTPUT);
pinMode(PB_7,OUTPUT);
pinMode(PB_6,INPUT);
// start the SPI library:
SPI.begin();
SPI.setDataMode(SPI_MODE0);
SPI.setClockDivider(SPI_CLOCK_DIV2);
delay(100);
}
void loop() {
unsigned char recievedData;
// recupération d'une valeur quelconque de quelque part
digitalWrite(chipSelectPin, LOW);
recievedData=SPI.transfer(dataToSend); //Send register location
delay(10);
digitalWrite(chipSelectPin, HIGH);
delay(1000);
//dataToSend=recievedData;
dataToSend++;
}
Ce programme a été testé avec notre esclave SPI sur FPGA décrit précédemment.
Câblage utilisé
[modifier | modifier le wikicode]On a utilisé le connecteur PMod nommé JC (partie basse) comme ceci :
Le câblage du PMod a été choisi comme :
SPI | -- | -- | MOSI | MISO | SCK | SS |
---|---|---|---|---|---|---|
PMOD | 6 (Vcc) | 5 (GND) | 4 | 3 | 2 | 1 |
et donc le fichier ucf correspondant pour la partie haute du connecteur JC :
#JC #Connecteur haut pour Tiva C LaunchPad NET "ss" LOC = "H3" | IOSTANDARD = "LVCMOS33"; NET "sck" CLOCK_DEDICATED_ROUTE = FALSE | LOC = "L7" | IOSTANDARD = "LVCMOS33"; NET "miso" LOC = "K6" | IOSTANDARD = "LVCMOS33"; NET "mosi" LOC = "G3" | IOSTANDARD = "LVCMOS33";
Côté TM4C123G on câble :
Tiva C LaunchPad | -- | GND | PB_7 | PB_6 | PB_4 | PB_5 |
---|---|---|---|---|---|---|
PMOD (Digilent) | 6 (Vcc) | 5 (GND) | 4 | 3 | 2 | 1 |
Communication i2c
[modifier | modifier le wikicode]Comme ce problème a été traité avec l'Arduino, nous allons faire une variation pour cette section. Nous allons utiliser un cœur i2c esclave écrit en VHDL. Nous avons fini par trouver un esclave i2c en VHDL... et très récemment un contrôleur i2c intelligent. Ce contrôleur développé par Mike Field mérite d’être étudié de près. Lui l'utilise dans quelques projets mais surtout en maître. Il nous faut donc comprendre son fonctionnement pour voir s'il n’est pas possible de l’utiliser en esclave.
Pour utiliser l'i2c sous Energia avec le TM4C123G on câble :
Périphérique i2c (numéro) | -- | GND | SCL(3) | SDA(3) |
---|---|---|---|---|
Tiva C LaunchPad | -- | GND | PD0 | PD1 |
PMOD (Digilent) | 6 (Vcc) | 5 (GND) | 4 | 3 |
Il faut savoir que l’on utilise le périphérique i2c portant le numéro 3 en câblant ainsi. C'est celui qui est utilisé par la bibliothèque Energia par défaut.
Peut-on utiliser ĺa DMA pour les transferts bidirectionnels avec les FPGA ?
[modifier | modifier le wikicode]La DMA permet des transferts directs entre périphériques et la mémoire RAM. Le module qui s'occupe de cela dans le processeur qui nous intéresse s’appelle μDMA. Il permet aussi des transferts directs de mémoire à mémoire mais nous ne nous y intéresserons pas.
Données du convertisseur Analogique Numérique vers le FPGA
[modifier | modifier le wikicode]Dans cette section, on cherche à réaliser un transfert monodirectionnel : du processeur TM4C123GH6PM vers le FPGA.
La conversion Analogique numérique sur ce type de processeur se fait à une fréquence d'environ 260 kHz (temps de conversion 3,9 μs). La question que l’on va chercher à résoudre est de réaliser des transferts SPI de ces valeurs au fur et à mesure qu’elles arrivent par DMA, donc sans utiliser de temps processeur (ou presque).
La fréquence de 260 kHz donnée ne correspond pas à la fréquence d'horloge du SPI. En effet à chaque conversion il faut transmettre 12 bits (soit deux octets car l'unité de base du SPI est l'octet). Cela fait donc une fréquence minimum de l'horloge du SPI de 4,160 000 MHz.
Voir aussi
[modifier | modifier le wikicode]- (en)Tiva-C LaunchPad
- Un exemple commercial de réalisation avec un FPGA de type Zynq : RedPitaya : un oscilloscope USB
Utiliser le tout nouveau MSP432 de Texas Instrument
[modifier | modifier le wikicode]Même si le type de microcontrôleur le plus vendu reste le 8 bits, il y a encore de la place pour le MSP430. Pourtant Texas Instrument a décidé d'étoffer son offre faible consommation (MSP43X) par un nouveau venu le MSP432 en 2015). Celui-ci est un 32 bits et nous allons l'étudier maintenant. Ce qui nous a séduit avec ce processeur est la facilité avec laquelle on fait du multitâche avec l'environnement Energia. Il est très possible qu'au moment où vous lisiez ces lignes l’utilisation du multitâche puisse se faire avec d'autres processeurs de chez Texas Instrument.
La communication correcte pour programmer par le PORT USB sous linux nécessite de mettre le fichier "71-ti-permissions.rules" que voici
SUBSYSTEM=="usb",ENV{DEVTYPE}=="usb_device",ATTRS{idVendor}=="0403",ATTRS{idProduct}=="a6d0",MODE:="0666" SUBSYSTEM=="usb",ENV{DEVTYPE}=="usb_device",ATTRS{idVendor}=="0403",ATTRS{idProduct}=="a6d1",MODE:="0666" SUBSYSTEM=="usb",ENV{DEVTYPE}=="usb_device",ATTRS{idVendor}=="0403",ATTRS{idProduct}=="6010",MODE:="0666" SUBSYSTEM=="usb",ENV{DEVTYPE}=="usb_device",ATTRS{idVendor}=="1cbe",ATTRS{idProduct}=="00fd",MODE:="0666" SUBSYSTEM=="usb",ENV{DEVTYPE}=="usb_device",ATTRS{idVendor}=="1cbe",ATTRS{idProduct}=="00ff",MODE:="0666" SUBSYSTEM=="usb",ENV{DEVTYPE}=="usb_device",ATTRS{idVendor}=="0451",ATTRS{idProduct}=="bef1",MODE:="0666" SUBSYSTEM=="usb",ENV{DEVTYPE}=="usb_device",ATTRS{idVendor}=="0451",ATTRS{idProduct}=="bef2",MODE:="0666" SUBSYSTEM=="usb",ENV{DEVTYPE}=="usb_device",ATTRS{idVendor}=="0451",ATTRS{idProduct}=="bef3",MODE:="0666" SUBSYSTEM=="usb",ENV{DEVTYPE}=="usb_device",ATTRS{idVendor}=="0451",ATTRS{idProduct}=="bef4",MODE:="0666" SUBSYSTEM=="usb",ENV{DEVTYPE}=="usb_device",ATTRS{idVendor}=="0451",ATTRS{idProduct}=="f432",MODE:="0666" SUBSYSTEM=="usb",ENV{DEVTYPE}=="usb_device",ATTRS{idVendor}=="0d28",ATTRS{idProduct}=="0204",MODE:="0666" KERNEL=="hidraw*",ATTRS{busnum}=="*",ATTRS{idVendor}=="0d28",ATTRS{idProduct}=="0204",MODE:="0666" ATTRS{idVendor}=="0451",ATTRS{idProduct}=="bef0",ENV{ID_MM_DEVICE_IGNORE}="1" ATTRS{idVendor}=="0c55",ATTRS{idProduct}=="0220",ENV{ID_MM_DEVICE_IGNORE}="1" KERNEL=="ttyACM[0-9]*",MODE:="0666" SUBSYSTEM=="usb", ATTRS{idVendor}=="0451", ATTRS{idProduct}=="c32a", MODE="0660", GROUP="dialout", RUN+="/sbin/modprobe ftdi-sio" RUN+="/bin/sh -c '/bin/echo 0451 c32a > /sys/bus/usb-serial/drivers/ftdi_sio/new_id'"
dans /etc/udev/rules.d si vous travaillez sous Linux.
Petit jeu de Pong avec carte Altera DE2-115 (Projet 2015/2016)
[modifier | modifier le wikicode]Pour changer un peu de FPGA, nous allons cibler une carte Altera. C'est la carte FPGA qui s'occupe de l’affichage sur l'écran VGA mais le processeur externe s'occupe des déplacements de la balle et de la raquette. Nous donnons un code simple qui dessine une balle et une raquette sur un écran VGA (les deux sont matérialisés par des rectangles).
library IEEE;
use IEEE.STD_LOGIC_1164.all;
ENTITY VGA_pong IS
PORT (clk_50 : in STD_LOGIC;
-- x_rect, y_rect: IN STD_LOGIC_VECTOR(9 DOWNTO 0); -- retirer commentaire pour question 2
-- y_raquG, y_raquD: IN STD_LOGIC_VECTOR(7 DOWNTO 0); -- retirer commentaire pour question 2
-- Port SPI pour esclave
mosi : in std_logic;
miso : out std_logic;
ss : in std_logic;
sck : in std_logic;
--leds
leds : out std_logic_vector(9 downto 0);
--VGA PORT
VGA_BLANK, VGA_SYNC,VGA_CLK: out STD_LOGIC;
hsynch,vsynch,red,green,blue : out STD_LOGIC);
END VGA_pong;
ARCHITECTURE atop of VGA_pong is
COMPONENT VGA_SYNCHRO IS
PORT( clock_50Mhz : IN STD_LOGIC;
horiz_sync_out, vert_sync_out : OUT STD_LOGIC;
VGA_BLANK, VGA_SYNC,VGA_CLK,RED,GREEN,BLUE, video_on : out STD_LOGIC;
pixel_row, pixel_column: OUT STD_LOGIC_VECTOR(9 DOWNTO 0));
END COMPONENT;
COMPONENT rect IS PORT(
row,col,x_rec,y_rec,delta_x,delta_y :in STD_LOGIC_VECTOR(9 DOWNTO 0); ----- on dit les entrees------
colorRGB : in STD_LOGIC_VECTOR(2 DOWNTO 0);
video_on : in std_logic;
red1,green1,blue1 : out std_logic); -----on dit les sorties ------
END COMPONENT;
component spi_slave is
Generic (
N : positive := 32; -- 32bit serial word length is default
CPOL : std_logic := '0'; -- SPI mode selection (mode 0 default)
CPHA : std_logic := '0'; -- CPOL = clock polarity, CPHA = clock phase.
PREFETCH : positive := 3); -- prefetch lookahead cycles
Port (
clk_i : in std_logic := 'X'; -- internal interface clock (clocks di/do registers)
spi_ssel_i : in std_logic := 'X'; -- spi bus slave select line
spi_sck_i : in std_logic := 'X'; -- spi bus sck clock (clocks the shift register core)
spi_mosi_i : in std_logic := 'X'; -- spi bus mosi input
spi_miso_o : out std_logic := 'X'; -- spi bus spi_miso_o output
di_req_o : out std_logic; -- preload lookahead data request line
di_i : in std_logic_vector (N-1 downto 0) := (others => 'X'); -- parallel load data in (clocked in on rising edge of clk_i)
wren_i : in std_logic := 'X'; -- user data write enable
wr_ack_o : out std_logic; -- write acknowledge
do_valid_o : out std_logic; -- do_o data valid strobe, valid during one clk_i rising edge.
do_o : out std_logic_vector (N-1 downto 0); -- parallel output (clocked out on falling clk_i)
--- debug ports: can be removed for the application circuit ---
do_transfer_o : out std_logic; -- debug: internal transfer driver
wren_o : out std_logic; -- debug: internal state of the wren_i pulse stretcher
rx_bit_next_o : out std_logic; -- debug: internal rx bit
state_dbg_o : out std_logic_vector (3 downto 0); -- debug: internal state register
sh_reg_dbg_o : out std_logic_vector (N-1 downto 0) -- debug: internal shift register
);
end component spi_slave;
signal s_row, s_col : STD_LOGIC_VECTOR(9 DOWNTO 0);
signal s_video_on, s_en : std_logic;
signal s_x_ball,s_y_ball,s_x_paddle : std_logic_vector(9 downto 0);
signal s_RED, s_RED1, s_GREEN, s_GREEN1, s_BLUE, S_BLUE1 : std_logic;
BEGIN
-------------------- liaison dans l'entite pong---------------
il: VGA_SYNCHRO port map(
clock_50Mhz => clk_50,
pixel_row => s_row,
pixel_column => s_col,
video_on => s_video_on,
horiz_sync_out => hsynch,
vert_sync_out => vsynch,
VGA_BLANK => VGA_BLANK,
VGA_SYNC => VGA_SYNC,
VGA_CLK=> VGA_CLK);
------------------ la balle ----------------------------
ball: rect port map(
row => s_row,
col => s_col,
x_rec => s_x_ball,
y_rec => s_y_ball,
delta_x => "0000001100",
delta_y => "0000001100",
colorRGB => "001",
video_on => s_video_on,
red1 => s_RED,
green1 => s_GREEN,
blue1 => s_BLUE );
------------------la raquette----------------------------
paddle: rect port map(
row => s_row,
col => s_col,
x_rec => s_x_paddle,
y_rec => "0101100100",
delta_x => "0000101100",
delta_y => "0000001100",
colorRGB => "010",
video_on => s_video_on,
red1 => s_RED1,
green1 => s_GREEN1,
blue1 => s_BLUE1 );
---------------------------------------------------------------
ic1 : spi_slave generic map (N => 32)
port map ( clk_i => clk_50,
do_o(9 downto 0) => s_x_ball,
do_o(19 downto 10) => s_y_ball,
do_o(29 downto 20) => s_x_paddle,
do_valid_o => s_en,
di_i => x"00000000",
spi_ssel_i => ss,
spi_sck_i => sck,
spi_mosi_i => mosi,
spi_miso_o => miso,
wren_i => '0' --not ss--s_wren_i
);
----------Construction des couleurs--------------------
RED <= s_RED OR s_RED1;
GREEN <= s_GREEN OR s_GREEN1;
BLUE <= s_BLUE OR s_BLUE1;
-- process(clk_50) begin
-- if rising_edge(clk_50) then
-- if s_en = '1' then
-- s_x_rec <= s_do_o;
-- end if;
-- end if;
-- end process;
leds <= s_x_ball;
END aTop;
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
use IEEE.STD_LOGIC_1164.all;
-- on a ajouté pixel_row et pixel column pour la suite
ENTITY VGA_SYNCHRO IS
PORT( clock_50Mhz : IN STD_LOGIC;
horiz_sync_out, vert_sync_out : OUT STD_LOGIC;
VGA_BLANK, VGA_SYNC,VGA_CLK,RED,GREEN,BLUE, video_on : out STD_LOGIC;
pixel_row, pixel_column: OUT STD_LOGIC_VECTOR(9 DOWNTO 0));
END VGA_SYNCHRO;
ARCHITECTURE aVGA_SYNC OF VGA_SYNCHRO IS
SIGNAL horiz_sync, vert_sync,clock_25Mhz : STD_LOGIC;
SIGNAL h_count, v_count :STD_LOGIC_VECTOR(9 DOWNTO 0);
BEGIN
div2:process(clock_50Mhz) begin
if rising_edge(clock_50Mhz) then
clock_25Mhz <= not clock_25Mhz;
end if;
end process;
-- Horiz_sync ------------------------------------__________--------
-- H_count 0 640 656 751 799
-- Bloc compteur du haut et gauche de la figure sur front montant
gestion_H_Count:PROCESS(clock_25Mhz) BEGIN
IF(clock_25Mhz'EVENT) AND (clock_25Mhz='1') THEN
IF (h_count = 799) THEN
h_count <= (others =>'0');
ELSE
h_count <= h_count + 1;
END IF;
END IF;
END PROCESS;
gestion_Horiz_sync: PROCESS(clock_25Mhz,h_count) BEGIN
--Bloc comparateur du haut et à droite de la figure synchronisé sur front descendants
IF(clock_25Mhz'EVENT) AND (clock_25Mhz='0') THEN
IF (h_count <= 751) AND (h_count >= 656) THEN --***** corrigée le 6 mars 2012
horiz_sync <= '0';
ELSE
horiz_sync <= '1';
END IF;
END IF;
END PROCESS;
-- Vert_sync -----------------------------------------------_______------------
-- V_count 0 480 490-491 524
-- Bloc compteur en bas à gauche de la figure
gestion_V_Count: PROCESS(clock_25Mhz,h_count) BEGIN
IF(clock_25Mhz'EVENT) AND (clock_25Mhz='1') THEN
IF (v_count >= 524) AND (h_count >= 699) THEN
v_count <= (others =>'0');
ELSIF (h_count = 699) THEN
v_count <= v_count + 1;
END IF;
END IF;
END PROCESS;
gestion_Vertical_sync:PROCESS(clock_25Mhz,v_count) BEGIN
IF(clock_25Mhz'EVENT) AND (clock_25Mhz='0') THEN
--Bloc comparateur du bas et à droite de la figure synchronisé sur front descendants
IF (v_count <= 491) AND (v_count >= 490) THEN --***** corrigé le 17 mai 2012
vert_sync <= '0';
ELSE
vert_sync <= '1';
END IF;
END IF;
END PROCESS;
pixel_column <= h_count;
pixel_row <= v_count;
horiz_sync_out <= horiz_sync;
vert_sync_out <= vert_sync;
VGA_BLANK <= '1';
VGA_SYNC <= '0';
VGA_CLK <= clock_25MHz;
video_on <= '1' when ((h_count < 640) and (v_count < 480)) else
'0';
END aVGA_SYNC;
------------------------------Entity of rect-------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
use IEEE.STD_LOGIC_1164.all;
ENTITY rect IS PORT(
row,col,x_rec,y_rec,delta_x,delta_y :in STD_LOGIC_VECTOR(9 DOWNTO 0); ----- on dit les entrees------
colorRGB : in STD_LOGIC_VECTOR(2 DOWNTO 0);
video_on : in std_logic;
red1,green1,blue1 : out std_logic); -----on dit les sorties ------
END rect;
----------------architecture of rect -------------------------------
ARCHITECTURE arect of rect is begin
PROCESS(row,col,x_rec,y_rec,video_on) BEGIN
if video_on = '1' then
if row > y_rec and row < y_rec+delta_y then
if col >x_rec and col < x_rec+delta_x then
red1 <= colorRGB(2);-- and video_on;
green1 <= colorRGB(1);-- and video_on;
blue1 <= colorRGB(0);-- and video_on;
else
red1 <= '0';
green1 <= '0';
blue1 <= '0';
end if;
else
red1 <= '0';
green1 <= '0';
blue1 <= '0';
end if;
else -- video_on = '0'
red1 <= '0';
green1 <= '0';
blue1 <= '0';
end if;
end process;
end arect;
Et voici le fichier de contraintes :
To,Direction,Location,I/O Bank,VREF Group,I/O Standard,Reserved clk_50,Input,PIN_Y2,2,B2_N0,3.3-V LVTTL, blue,Output,PIN_D12,8,B8_N0,3.3-V LVTTL, VGA_BLANK,Output,PIN_F11,8,B8_N1,3.3-V LVTTL, VGA_CLK,Output,PIN_A12,8,B8_N0,3.3-V LVTTL, green,Output,PIN_C9,8,B8_N1,3.3-V LVTTL, hsynch,Output,PIN_G13,8,B8_N0,3.3-V LVTTL, red,Output,PIN_H10,8,B8_N1,3.3-V LVTTL, VGA_SYNC,Output,PIN_C10,8,B8_N0,3.3-V LVTTL, vsynch,Output,PIN_C13,8,B8_N0,3.3-V LVTTL, # SPI ss,Input,PIN_AC15,4,B4_N2,3.3-V LVTTL, sck,Input,PIN_Y17,4,B4_N0,3.3-V LVTTL, mosi,Input,PIN_Y16,4,B4_N0,3.3-V LVTTL, miso,Output,PIN_AE16,4,B4_N2,3.3-V LVTTL, # leds LEDS[9],Output,PIN_G17,7,B7_N1,2.5 V, LEDS[8],Output,PIN_J17,7,B7_N2,2.5 V, LEDS[7],Output,PIN_H19,7,B7_N2,2.5 V, LEDS[6],Output,PIN_J19,7,B7_N2,2.5 V, LEDS[5],Output,PIN_E18,7,B7_N1,2.5 V, LEDS[4],Output,PIN_F18,7,B7_N1,2.5 V, LEDS[3],Output,PIN_F21,7,B7_N0,2.5 V, LEDS[2],Output,PIN_E19,7,B7_N0,2.5 V, LEDS[1],Output,PIN_F19,7,B7_N0,2.5 V, LEDS[0],Output,PIN_G19,7,B7_N2,2.5 V,
Nous rappelons que le code donné ci-dessus nécessite l'ajout d'un fichier spi_slave.vhd dans le projet, fichier que l’on trouve chez Opencores.org : SPI Master Slave de Jonny Doin.
Cette version utilise un échange SPI de 32 bits. Les données sont supposées être :
- 10 bits de poids faible (b9 à b0) pour la coordonnée x de la balle
- 10 bits de poids moyens (b19 à b10) pour la coordonnée y de la balle
- 10 bits de poids forts (b29 à b20) pour la coordonnée x de la raquette
Une version d'essai du programme est :
#include <SPI.h>
//**** Version pour MSP432 {{Unité|3.3|{{Abréviation|V|volt}}}}
uint32_t dataToSend=0x00;
void setup (void) {
// Put SCK, MOSI, SS pins into output mode
// also put SCK, MOSI into LOW state, and SS into HIGH state.
pinMode(7,OUTPUT); //SCK
pinMode(18,OUTPUT); // SS dedicated
pinMode(15,OUTPUT); // MOSI
pinMode(14,INPUT); // MISO
// ensure SS stays high for now
digitalWrite(18, HIGH);
digitalWrite(7, LOW);
digitalWrite(7, HIGH);
digitalWrite(7, LOW);
delay(1);
digitalWrite(15, LOW);
// Then put SPI hardware into Master mode and turn SPI on
SPI.begin();
// Slow down the master a bit
SPI.setClockDivider(SPI_CLOCK_DIV2);
//little indian or big indian ?
// SPI.setBitOrder(MSBFIRST);
// SPI.setBitOrder(LSBFIRST);
// SPI.setDataMode(SPI_MODE0);
delay(100);
} // end of setup
void loop (void) {
//déclaration variable
uint16_t recievedData;
// enable Slave Select (SS is pin 10)
digitalWrite(18, LOW);
// transmission réception des 32 bits
recievedData=SPI.transfer(dataToSend>>24);
recievedData=SPI.transfer(dataToSend>>16);
recievedData=SPI.transfer(dataToSend>>8);
recievedData=SPI.transfer(dataToSend);
digitalWrite(18, HIGH);
dataToSend +=5;
if ((dataToSend & 0x3FF) > 639) dataToSend &= ~0x3FF;
dataToSend += 0x00001400;
if ((dataToSend & 0xFFC00) > 0x6C000) dataToSend &= ~0xFFC00;
// 50 milliseconds delay
delay(50);
} // end of loop
Les nombres hexadécimaux bizarres apparaissant dans ce code sont liés aux conventions pour les coordonnées des objets balle et raquette.
- Il ne faut surtout pas mettre de delay entre les quatre transferts SPI successifs !
- La partie matérielle ne fonctionne pas correctement : le bit de poids faible est systématiquement perdu !!! Nous n'avons pas réussi à résoudre ce problème matériel pour le moment... donc avons contourné le problème avec du logiciel...
Il y a au moins deux façons de contourner le problème de cette remarque de manière logicielle :
- faire un décalage d'un bit vers la gauche avant l'envoi SPI : on perd alors systématiquement le bit de poids faible.
- considérer une autre répartitions des 3x10 bits comme ceci :
Répartition espérée :
b31
|
b30
|
b29
|
b28..b21
|
b20
|
b19
|
b18..b11
|
b10
|
b9
|
b8..b1
|
b0
|
X
|
X
|
RaqX9
|
RaqX8..RaqX1
|
RaqX0
|
BallY9
|
BallY8..BallY1
|
BallY0
|
BallX9
|
BallX8..BallX1
|
BallX0
|
Le matériel réalise donc la répartition des 3 données de 10 bits comme montré ci-dessus. Mais un problème matériel (perte du bit de poids faible) nous oblige à considérer que la répartition est décalée d'un bit vers la gauche.
Répartition réalisée d'un point de vue logicielle :
b31
|
b30
|
b29..b22
|
b21
|
b20
|
b19..b12
|
b11
|
b10
|
b9..b2
|
b1
|
b0
|
X
|
RaqX9
|
RaqX8..RaqX1
|
RaqX0
|
BallY9
|
BallY8..BallY1
|
BallY0
|
BallX9
|
BallX8..BallX1
|
BallX0
|
X
|
Pour la deuxième fois, nous insistons sur le fait que nous avons l'intention de corriger le tour de passe-passe réalisé par le logiciel. Comme nous ne voyons pas très bien où est vraiment le problème, cela pourra prendre un peu de temps.
Voici le code associé :
void loop (void) {
//déclaration variable
uint16_t recievedData;
// enable Slave Select (SS is pin 10)
digitalWrite(18, LOW);
// transmission réception
// dataToSend <<= 1;
recievedData=SPI.transfer(dataToSend>>24);
recievedData=SPI.transfer(dataToSend>>16);
recievedData=SPI.transfer(dataToSend>>8);
//delay(1);
recievedData=SPI.transfer(dataToSend);
//delay(1);
digitalWrite(18, HIGH);
// dataToSend >>= 1;
dataToSend +=10; // 5<<1
if ((dataToSend & 0x7FF) > (639<<1)) dataToSend &= ~0x7FF;
dataToSend += 0x00002800;
if ((dataToSend & 0x1FF800) > (400<<11)) dataToSend &= ~0x1FF800;
// 50 milliseconds delay
delay(50);
} // end of loop
Pour éviter toute cette gymnastique, nous allons réaliser des sous-programmes utiles pour notre projet.
Écriture de sous-programmes bien utiles
[modifier | modifier le wikicode]Le fait de construire une variable unique de 32 bits contenant 3 positions de 10 bits complique un peu le design des sous programmes. Nous proposons deux sous-programmes pour gérer respectivement les positions de la balle et de la raquette.
Commençons par le positionnement de la balle :
//**********************************************************************************************************
// function setBallXY()
// purpose: put the ball with x and y coordinates
// arguments:
// corresponding x and y coordinates and the old 32-bit value
// return:32-bit ready for SPI
// note:all value are one bit left shifted because of hardware problem
//**********************************************************************************************************
uint32_t setBallXY(int16_t x,int16_t y,uint32_t old) {
uint32_t result=old;
// on met à zéro la partie que l’on va changer
result &= ~0x001FFFFF;
if ((x > 0) && (x < (630<<1))) result |= (x<<1);
if ((y > 0) && (y < (460<<11))) result |= (y<<11);
return result;
}
Voici comment il peut être utilisé :
void loop (void) {
//déclaration variable
uint8_t recievedData;
static int16_t ballX=40,ballY=40;
static int16_t deltaX=5,deltaY=5;
// enable Slave Select (SS is pin 10)
digitalWrite(18, LOW);
recievedData=SPI.transfer(dataToSend>>24);
recievedData=SPI.transfer(dataToSend>>16);
recievedData=SPI.transfer(dataToSend>>8);
recievedData=SPI.transfer(dataToSend);
digitalWrite(18, HIGH);
ballX += deltaX;
ballY += deltaY;
if (ballX > 620) deltaX = -deltaX;
if (ballX < 10) deltaX = -deltaX;
if (ballY > 430) deltaY = -deltaY;
if (ballY < 10) deltaY = -deltaY;
dataToSend = setBallXY(ballX,ballY,dataToSend);
// 50 milliseconds delay
delay(50);
} // end of loop
Voici maintenant un sous-programme bien utile pour le mouvement de la raquette :
//**********************************************************************************************************
// function setPaddleX()
// purpose: put the paddle at x position
// arguments:
// corresponding x and y coordinates and the old 32-bit value
// return:32-bit ready for SPI
// note:all value are one bit left shifted because of hardware problem
//**********************************************************************************************************
uint32_t setPaddleX(int16_t x,uint32_t old) {
uint32_t result=old;
// on met à zéro la partie que l’on va changer
result &= ~0x7FE00000;
if ((x > 0) && (x < (630<<1))) result |= (x<<21);
return result;
}
Améliorations
[modifier | modifier le wikicode]L'objectif du projet est naturellement d'améliorer le matériel et sa programmation. Il serait bien, par exemple, d’utiliser une manette Nunchuck pour déplacer la raquette.
Essai d'une manette Nuncuk avec MSP432
[modifier | modifier le wikicode]Nous n'avons pas encore essayé d’utiliser notre module matériel i2c que l’on a développé dans le projet Pacman en le portant pour Altera. Une des raisons est que nous ne savons pas comment utiliser des pull-up avec Altera (dans le fichier de contraintes). Une autre est le manque de temps... et une dernière est que nous avons l'intention d'abandonner l'i2c avec machine d'états pour le remplacer par ce processeur dédié. Cela aussi prendra du temps mais nous avons d'autres périphériques i2c en réserve, comme le MPU6050, qui seront plus facile à adapter avec ce processeur dédié à l'i2c. Ce périphérique pourrait d'ailleurs lui aussi être utilisé comme gamepad pour le jeu de pong que nous sommes en train de réaliser.
Puisque nous ne développons pas de matériel à ce stade, et que nous avons un processeur externe (MSP432) qui communique avec le FPGA nous pouvons essayer de communiquer avec la manette Nunchuk à l'aide de ce processeur.
Pour information, les programmes que l’on trouve pour l'Arduino pour lire une manette Nunchuk ne fonctionnent pas tels quels. Nous avons très vite suspecté le bit des données (SDA) parce que les valeurs reçues était toutes qu'avec des '1' partout :
- 255 pour le joystick analogique
- 1024 pour les accéléromètres
- 1 pour chacun des boutons.
Nous avons essayé de forcer l'entrée des données avec une résistance de tirage vers Vcc et cela a fonctionné immédiatement. Voici donc le code correspondant :
/********************************************************************************
* Code for read data from Wii nunchuck., base on http://www.windmeadow.com/node/42
* File : ArduinoNunchuk.pde
* by : K.Worapoht
* Hardware: MSP432, Arduino, POP-168 , AVR ATMega168 ,
* MSP432 : connect SCL to P6.5 and SDA to P6.4
* Arduino : connect SDA to PC4 (An4) and SCL PC5 (An5)
*******************************************************************************/
#include <Wire.h>
#define nunchuk_ID 0xA4 >> 1
unsigned char buffer[6];// array to store arduino output
int cnt = 0;
void setup ()
{
pinMode(10,INPUT_PULLUP); // SDA
Wire.begin(); // join i2c bus with address 0x52
nunchuck_init(); // send the initilization handshake
Serial.begin(9600);
delay(100);
}
void nunchuck_init()
{
Wire.beginTransmission (nunchuk_ID);// transmit to device 0x52
Wire.write(0x40); // sends memory address
Wire.write(0x00); // sends sent a zero.
Wire.endTransmission(); // stop transmitting
}
void send_zero()
{
Wire.beginTransmission(nunchuk_ID);// transmit to device 0x52
Wire.write(0x00); // sends one byte
Wire.endTransmission(); // stop transmitting
}
void loop ()
{
//nunchuck_init();
//send_zero();
Wire.requestFrom(nunchuk_ID, 6);// request data from nunchuck
while (Wire.available())
{
buffer[cnt] = nunchuk_decode_byte (Wire.read()); // receive byte as an integer
cnt++;
}
// If we recieved the 6 bytes, then go print them
if (cnt >= 5)
{
print();
}
cnt = 0;
send_zero(); // send the request for next bytes
delay (500);
}
// Print the input data we have recieved
// accel data is 10 bits long
// so we read 8 bits, then we have to add
// on the last 2 bits.
void print()
{
unsigned char joy_x_axis;
unsigned char joy_y_axis;
int accel_x_axis;
int accel_y_axis;
int accel_z_axis;
unsigned char z_button;
unsigned char c_button;
joy_x_axis = buffer[0];
joy_y_axis = buffer[1];
accel_x_axis = (buffer[2]) << 2;
accel_y_axis = (buffer[3]) << 2;
accel_z_axis = (buffer[4]) << 2;
// byte outbuf[5] contains bits for z and c buttons
// it also contains the least significant bits for the accelerometer data
// so we have to check each bit of byte outbuf[5]
if ((buffer[5] & 0x01)!=0)
{ z_button = 1; }
else
{ z_button = 0; }
if ((buffer[5] & 0x02)!=0)
{ c_button = 1; }
else
{ c_button = 0; }
accel_x_axis += ((buffer[5]) >> 2) & 0x03;
accel_y_axis += ((buffer[5]) >> 4) & 0x03;
accel_z_axis += ((buffer[5]) >> 6) & 0x03;
Serial.print (joy_x_axis, DEC);
Serial.print ("\t");
Serial.print (joy_y_axis, DEC);
Serial.print ("\t");
Serial.print (accel_x_axis, DEC);
Serial.print ("\t");
Serial.print (accel_y_axis, DEC);
Serial.print ("\t");
Serial.print (accel_z_axis, DEC);
Serial.print ("\t");
Serial.print (z_button, DEC);
Serial.print ("\t");
Serial.print (c_button, DEC);
Serial.print ("\r\n");
}
// Encode data to format that most wiimote drivers except
// only needed if you use one of the regular wiimote drivers
char nunchuk_decode_byte(char x)
{
x = (x ^ 0x17) + 0x17;
return x;
}
Le passage de la broche de données en PULLUP n’est pas obligatoire avec l'Arduino. Mais, c'est le seul moyen que nous avons trouvé pour faire fonctionner la manette Nunchuk avec le MSP432.
Réunir tout le monde maintenant
[modifier | modifier le wikicode]Tout ce que nous avons besoin pour mener le projet à bien est terminé du point de vue matériel. Seul le logiciel reste à développer sur le MSP432. Celui-ci doit permettre de déplacer la raquette grâce à la manette Nunchuk, de déteminer les rebonds de la balle sur cette même raquette.
Voici une version sans calcul des rebonds. La balle se déplace de manière indépendante et la raquette se déplace grâce à la manette Nunchuk.
//**** Version pour MSP432 {{Unité|3.3|{{Abréviation|V|volt}}}} **********
#include <SPI.h>
#include <Wire.h>
#define nunchuk_ID 0xA4 >> 1
unsigned char buffer[6];// array to store MSP432 output
int cnt = 0;
uint32_t dataToSend=0x00;
void nunchuck_init()
{
Wire.beginTransmission (nunchuk_ID);// transmit to device 0x52
Wire.write(0x40); // sends memory address
Wire.write(0x00); // sends sent a zero.
Wire.endTransmission(); // stop transmitting
}
void send_zero()
{
Wire.beginTransmission(nunchuk_ID);// transmit to device 0x52
Wire.write(0x00); // sends one byte
Wire.endTransmission(); // stop transmitting
}
void setup (void) {
// Put SCK, MOSI, SS pins into output mode
// also put SCK, MOSI into LOW state, and SS into HIGH state.
pinMode(7,OUTPUT); //SCK
pinMode(18,OUTPUT); // SS dedicated
pinMode(15,OUTPUT); // MOSI
pinMode(14,INPUT); // MISO
// ensure SS stays high for now
digitalWrite(18, HIGH);
digitalWrite(7, LOW);
digitalWrite(7, HIGH);
digitalWrite(7, LOW);
delay(1);
digitalWrite(15, LOW);
// Then put SPI hardware into Master mode and turn SPI on
SPI.begin();
// Slow down the master a bit
SPI.setClockDivider(SPI_CLOCK_DIV32);
//little indian or big indian ?
SPI.setBitOrder(MSBFIRST);
// SPI.setBitOrder(LSBFIRST);
SPI.setDataMode(SPI_MODE0);
pinMode(10,INPUT_PULLUP); //nécessaire sur MSP432
Wire.begin(); // join i2c bus with address 0x52
nunchuck_init(); // send the initilization handshake
delay(100);
} // end of setup
//**********************************************************************************************************
// function setBallXY()
// purpose: put the ball with x and y coordinates
// arguments:
// corresponding x and y coordinates and the old 32-bit value
// return:32-bit ready for SPI
// note:all value are one bit left shifted because of hardware problem
//**********************************************************************************************************
uint32_t setBallXY(int16_t x,int16_t y,uint32_t old) {
uint32_t result=old;
// on met à zéro la partie que l’on va changer
result &= ~0x001FFFFF;
if ((x > 0) && (x < (630<<1))) result |= (x<<1);
if ((y > 0) && (y < (460<<11))) result |= (y<<11);
return result;
}
//**********************************************************************************************************
// function setPaddleX()
// purpose: put the paddle at x position
// arguments:
// corresponding x and y coordinates and the old 32-bit value
// return:32-bit ready for SPI
// note:all value are one bit left shifted because of hardware problem
//**********************************************************************************************************
uint32_t setPaddleX(int16_t x,uint32_t old) {
uint32_t result=old;
// on met à zéro la partie que l’on va changer
result &= ~0x7FE00000;
if ((x > 10) && (x <= (630))) result |= (x<<21);
return result;
}
// Encode data to format that most wiimote drivers except
// only needed if you use one of the regular wiimote drivers
char nunchuk_decode_byte(char x)
{
x = (x ^ 0x17) + 0x17;
return x;
}
void loop (void) {
//déclaration variable
uint8_t recievedData;
static int16_t ballX=40,ballY=40;
static int16_t deltaX=5,deltaY=5;
static int16_t posX=0;
// lecture manette Nunchuk
Wire.requestFrom(nunchuk_ID, 6);// request data from nunchuck
while (Wire.available()){
buffer[cnt] = nunchuk_decode_byte (Wire.read()); // receive byte as an integer
cnt++;
}
if (cnt >= 5) {
if (buffer[0] > 150)
posX += 10;
if (posX > 630) posX = 630;
if (buffer[0] < 80)
if (posX > 10)
posX -= 10;
else
posX = 0;
}
cnt = 0;
send_zero(); // send the request for next bytes
// enable Slave Select (SS is pin 10)
digitalWrite(18, LOW);
recievedData=SPI.transfer(dataToSend>>24);
recievedData=SPI.transfer(dataToSend>>16);
recievedData=SPI.transfer(dataToSend>>8);
recievedData=SPI.transfer(dataToSend);
digitalWrite(18, HIGH);
ballX += deltaX;
ballY += deltaY;
if (ballX > 620) deltaX = -deltaX;
if (ballX < 10) deltaX = -deltaX;
if (ballY > 430) deltaY = -deltaY;
if (ballY < 10) deltaY = -deltaY;
dataToSend = setBallXY(ballX,ballY,dataToSend);
dataToSend = setPaddleX(posX,dataToSend);
// 50 milliseconds delay
delay(50);
} // end of loop
Voir aussi
[modifier | modifier le wikicode]- Périqhérique MPU6050 dans ce livre aborde maintenant l'I2C avec la carte Altera DE2-115