Very High Speed Integrated Circuit Hardware Description Language/Travail pratique/Utiliser des shields Arduino avec les FPGA
Comme son titre l'indique, ce chapitre est destiné à étudier les liaisons dangereuses (ou pas) entre les shields destinés aux Arduinos (5 V) et les FPGA (3,3 V). Ici, le mot shield sera pris dans un sens très large : tout ce qui se connecte à l'Arduino rapidement, y compris avec des fils et des plaques à essais.
Ce chapitre ne peut pas être vu comme un seul TP. Nous avons regroupé dans ce chapitre un certain nombre de sujets assez disparates qui utilisent différents capteurs interfacés à un FPGA. L'enseignant intéressé pourra ainsi y trouver des idées qu'il assemblera lui même. L'étudiant pourra trouver des exercices qui sont en général corrigés.
La très grande majorité des shields Arduino sont prévus pour être alimenté en 5 V. L'utilisation de ces shields avec des FPGA qui fonctionnent en 3,3 V nécessite donc un certain nombre de précautions. Celui qui craint le plus dans l'histoire est naturellement le FPGA.
Il existe des cartes FPGA avec un connecteur de type Arduino, donc capable de recevoir un shield :
- la DE0 nano Soc de chez Terasic en est un premier exemple. Elle est équipée d'un cyclone V de chez Altera(Euh... pardon, de chez Intel) . Le connecteur Arduino de cette carte fournit du 5 V à l'endroit normal du 5 V des cartes Arduino. Cela veut dire que si vous mettez un shield sur cette carte, vous risquez d'endommager le FPGA. Il suffit d'avoir 3 boutons en pull-up et vous avez automatiquement du 5 V sur quelques entrées du FPGA !
- la DE10-lite du même constructeur possède aussi un connecteur Arduino.
- La carte Arty en est un autre exemple de chez Digilent. Nous ne l'avons pas encore exploré (car nous n'en possédons pas) et ne savons donc pas comment est géré le 5 V. Une phrase dans la documentation nous laisse penser qu'on a le même type de problème qu'avec la carte précédente : The Arty is not compatible with shields that output 5 V digital or analog signals. Driving pins on the Arty shield connector above 5 V may cause damage to the FPGA.
Pour cela nous avons décidé de faire nos propres shields en gardant quelques bonnes idées des shields commercialisés. Il est difficile cependant de se mettre un bandeau sur les yeux et de ne pas regarder vers l'orient. Même si construire est une idée raisonnable pour un établissement qui enseigne l'électronique et donc la fabrication de cartes, les bas prix des shields venant de Chine la met à mal pour les hobbyistes et même les enseignants qui ont des budgets de plus en plus serrés. Nous avons en effet trouvé un shield avec quelques boutons et un afficheur LCD de deux lignes de 16 caractères pour un peu moins de 4 € et un shield multifonction pour un peu plus de 5 €. Tous les deux ont été testés et fonctionnent parfaitement avec des Arduino. Nous allons donc explorer leur utilisation dans ce chapitre en remplaçant l'Arduino par un FPGA. Et nous en profiterons pour nous intéresser à bien d'autres shields et capteurs...
Pour les cartes Xilinx de Digilent, pensez à relire les connecteurs PMod pour comprendre cette connectique.
Étude du shield multi fonction
[modifier | modifier le wikicode]Le shield multifonction est décrit par exemple ici.
Cette carte est prévue pour un fonctionnement en 5 V. Les 4 digits d'affichage sept segments sont pilotés par deux registres à décalage qu'il nous faudra étudier pour bien comprendre ce que l'on cherche à réaliser. Le point important à ce stade est qu'ils ont été choisis en CMOS (74HC595) et que par conséquent le 3,3 V ne leur fait pas peur. L'autre problème éventuel est de savoir si les afficheurs fonctionnent en 3,3 V. La réponse est OUI.
Il est possible de prendre le 3,3 V des connecteurs FPGA PMOD (ou de ceux d'Altera) pour alimenter le shield multifonction. Pour cela il ne faut surtout pas amener le 3,3 V du FPGA sur la broche marquée 3,3 V du shield mais sur celle qui est marquée 5 V. Le 5 V dont il est question ici n'est qu'une étiquette, c'est l'unique tension d'alimentation du shield. Il aura donc comme alimentation unique du 3,3 V connecté à la place du 5 V.
Présentation
[modifier | modifier le wikicode]Prenez le code d'exemple présenté ici. Nous vous conseillons fortement son utilisation avec une carte Arduino pour une prise de contact rapide. On reproduit ce code ici
/* Define shift register pins used for seven segment display */
#define LATCH_DIO 4
#define CLK_DIO 7
#define DATA_DIO 8
/* Segment byte maps for numbers 0 to 9 */
const byte SEGMENT_MAP[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0X80,0X90};
/* Byte maps to select digit 1 to 4 */
const byte SEGMENT_SELECT[] = {0xF1,0xF2,0xF4,0xF8};
void setup ()
{
/* Set DIO pins to outputs */
pinMode(LATCH_DIO,OUTPUT);
pinMode(CLK_DIO,OUTPUT);
pinMode(DATA_DIO,OUTPUT);
}
/* Main program */
void loop()
{
/* Update the display with the current counter value */
WriteNumberToSegment(0 , 0);
WriteNumberToSegment(1 , 1);
WriteNumberToSegment(2 , 2);
WriteNumberToSegment(3 , 3);
}
/* Write a decimal number between 0 and 9 to one of the 4 digits of the display */
void WriteNumberToSegment(byte Segment, byte Value)
{
digitalWrite(LATCH_DIO,LOW);
shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_MAP[Value]);
shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_SELECT[Segment] );
digitalWrite(LATCH_DIO,HIGH);
}
Ce programme est sensé afficher 0123 sur les 4 digits. Il montre aussi quelques détails importants sur le câblage :
- le transfert des données se fait à l'aide de trois fils (seulement) et c'est ce qui en fait tout son intérêt. Ces trois fils sont décrit maintenant
- le fil de donnée DATA_DIO est câblé en broche 8 Arduino
- le fil d'horloge de transfert CLK_DIO est câblé en broche 7 Arduino
- le fil de mémorisation est câblé en broche 4
Ces informations sont vitales pour nous.
Revenons un peu sur cette histoire des trois fils. Un afficheur sept segments 4 digits comporte au moins 12 fils :
- 7 fils pour chacun des segments
- 1 fil pour le point
- 4 fils pour la sélection de chacun des digits.
Réussir à n'utiliser que trois sorties pour l'utiliser est donc une idée intéressante. Elle est réalisable en remplaçant l'écriture parallèle par une écriture série. Ce qui rend la mise en œuvre délicate est le multiplexage des afficheurs : il faut mettre à jour sans arrêt l'affichage pour faire croire au lecteur qu'il est simultané sur les quatre afficheurs alors qu'il n'en n'est rien. En programmation, ceci peut être réalisé à l'aide d'une interruption qui s'occupe des transferts dans les deux 74HC595. En VHDL, le matériel doit s'occuper de tout cela.
Notre montage d'essai
[modifier | modifier le wikicode]Nous n'avons pas beaucoup de fils à brancher à cause de l'utilisation des registres à décalage. 5 fils en tout (alimentation comprise). Le connecteur PMod des cartes Digilent est déjà décrit dans un autre chapitre et permet de brancher 6 fils (y compris alimentation). Un simple connecteur PMod est donc suffisant et donc facile à trouver sur les cartes Digilent.
- le Vcc 3,3 V de PMod est amené au 5 V du shield
- La masse de PMod est amenée à la masse du shield
- le suivant, fil de donnée, est amené à la broche 8 du shield
- le suivant, fil d'horloge, est amené à la broche 7 du shield
- le suivant, horloge de mémorisation, est amené à la broche 4 du shield.
Tous les numéros apparaissant dans cette description correspondent à la sériegraphie du shield.
Évidemment, une connexion aussi simple ne permet pas d'avoir accès à toutes les fonctionnalités du shield. Pour cet exemple seuls les 4 digits des afficheurs sept segments sont gérés par le FPGA. Si vous voulez utiliser l'information des boutons poussoirs il vous faut savoir qu'ils sont montés avec une résistance de tirage vers le haut (pull-up) et sont connectés aux broches serigraphiées A1, A2 et A3 du shield. Donc trois fils en plus. Les 4 leds sont reliées à 13, 12, 11 et 10 encore 4 fils. Le buzzer est relié à 3 avec donc un seul fil.
Du VHDL pour afficher une donnée sur 4 digits
[modifier | modifier le wikicode]Résoudre ce problème est pédagogiquement très intéressant pour l'étude des registres à décalages et aussi pour la gestion d'un séquencement à l'aide d'un compteur.
Dans la suite nous serons amenés à réaliser deux types de signaux :
- un tick : signal à "un" pendant une seule période d'horloge globale du FPGA (nous utiliserons cette dénomination anglo-saxonne dans la suite de ce chapitre)
- une horloge : signal qui reste à 1 sur plusieurs fronts et à 0 sur plusieurs fronts de l'horloge globale du FPGA
Le tick ne servira qu'à l'intérieur du FPGA pour réaliser des montages synchrones tandis que l'horloge pilotera des circuits externes incapables de fonctionner à la fréquence du FPGA. Tout ceci va très vite devenir plus clair avec les mises en pratiques dans les exercices qui suivent.
- A ce stade on vous demande de bien faire la différence entre l'horloge et l'horloge globale. La première va être fabriquée à partir de la deuxième qui est la seule disponible quand vous achetez votre carte FPGA. Cette deuxième est typiquement de 50 ou 100 MHz sur les cartes que nous utilisons c'est-à-dire pas très récentes.
- Quand on vous dit qu'un tick ne dure qu'une période d'horloge globale, cela veut dire qu'il ne dure absolument pas longtemps (20ns pour 50MHz).
Introduction : Exercice 1
[modifier | modifier le wikicode]Nous allons commencer par réaliser un compteur 16 bits. Nous allons afficher son comptage sur les Leds disponibles : 8 sur la Nexys 3 (Digilent) et 16 sur la DE2-115 (Terasic). Il est donc impossible d'utiliser l'horloge 100MHz de la carte Nexys 3 et espérer voir quelque chose à l’œil. Il faut donc utiliser un autre compteur pour diminuer cette fréquence. Ceci a été présenté plusieurs fois dans ce livre et ailleurs :
Mais à chaque fois il s'agissait de prendre le bit de poids fort du compteur et de l'utiliser comme horloge de l'étage suivant. Cette façon de faire est assez facile à comprendre mais déconseillée pour une utilisation dans un FPGA. On doit lui préférer la méthode synchrone : toutes les horloges de tous les composants sont reliées à une horloge commune : l'horloge globale.
Nous allons donc réaliser l'ensemble demandé dans les règles de l'art : les deux compteurs utiliseront la même horloge. On vous demande de réaliser l'ensemble présenté par le schéma ci-dessus, en commençant par trouver l'équation combinatoire demandée. Il s'agit de réaliser un tick sur ENO, c'est-à-dire un signal qui ne vaut un que pendant une seule période d'horloge : l'horloge globale.
EN est un diminutif de enable en anglais que l'on peut traduire par autorisation
ENO est un diminutif de "enable output" en anglais que l'on peut traduire par sortie d'autorisation
- Lorsque, comme ici sur le schéma ci-dessus, la sortie ENO n'a pas d'entrée correspondante appelée EN dans le composant à réaliser, une simple comparaison sur la valeur du compteur suffit pour fabriquer ENO
- Lorsque la sortie ENO a une entrée correspondante appelée EN dans le composant à réaliser, une double comparaison sur la valeur du compteur et sur la valeur à un de EN fabrique ENO
Ceci a déjà été rencontré quand nous avons eu à cascader des compteurs BCD.
Le multiplexeur et le transcodeur : Exercice 2
[modifier | modifier le wikicode]On vous donne le schéma complet de ce que l'on cherche à faire dans la figure ci-contre. On vous demande de réaliser le multiplexeur et le transcodeur (à droite du schéma).
Le multiplexeur ne présente aucune difficulté de réalisation.
Le transcodeur non plus. Il vous faut seulement savoir qu'il faut un '0' pour allumer un segment et que le segment g est poids faible.
En fin de l'exercice 2 vous aurez donc le compteur 25 bits, le compteur 16 bits, le multiplexeur commandé par 4 interrupteurs et le transcodeur.
Cet ensemble peut être testé sur les afficheurs de votre carte FPGA. Vous prendrez quatre interrupteurs pour la sélection même si le séquenceur de l'exercice 3 n'en délivrera que deux. Cela vous permettra de savoir comment on sélectionne une seul afficheur à la fois et surtout ce qui se passe lorsque vous en sélectionnez deux.
Le séquenceur : Exercice 3
[modifier | modifier le wikicode]Comme d'habitude, l'affichage consiste à réaliser des choses visibles et d'autres non visibles. Par exemple, faire défiler des nombres est intéressant si cela est visible. Mais la commutation des afficheurs, elle, reste invisible. Ceci nécessite la réalisation de plusieurs fréquences :
- une d'environ 2,98 Hz (= 100MHz / 2**25) nécessite donc un compteur de 25 bits sera utilisée pour faire défiler un compteur de 16 bits qui sera affiché
- une d'environ 1,56 MHz (= 100MHz / 2**6) servira à envoyer les données au shield (servira d'horloge de données)
- une d'environ 1525 Hz (= 100MHz / 2**16) servira à commuter les afficheurs
Nous allons essayer de respecter les règles matérielles des FPGA pour les horloges en question. Cela veut dire qu'en fait ce ne seront pas des horloges mais des signaux de validation. Nous n'avons pas assez utilisé cette règle dans ce livre et essayerons de résoudre ce problème plus tard.
Réaliser le séquenceur. La difficulté, bien sûr est de réaliser une partie matérielle qui est en fait du combinatoire. Nous allons vous aider en vous présentant un extrait de la table de vérité (figure ci-contre). Une bonne compréhension de celle-ci est nécessaire pour résoudre ce problème.
Une bonne exploration de la table de séquencement vous montre que :
- les bits Q17 et Q16 serviront à la sélection du digit en cours d'affichage : il y en a 4 donc deux bits suffisent (cela n'apparaît pas sur la table de séquencement)
- les bits Q24 à Q18 ne sont pas utiles au séquencement : les équations logiques ne les feront donc pas apparaître
- il y a deux types de sortie :
- tick ne sont définis que pour une période d'horloge : Eno et Load. Ils seront utiles au fonctionnement interne, c'est-à-dire au FPGA mais pas au shield directement.
- horloges utiles à l'extérieure : Done. On ne peut pas prendre un tick pour cela car cela serait bien trop rapide pour les circuits 74HC595D du shield.
- le 0->1->0 qui apparaît en dernière colonne a pour signification que l'on doit réaliser une période d'horloge sur une ligne. Le seul moyen de faire cela est d'utiliser le bit de poids immédiatement plus petit, à savoir Q5.
1°) Exprimer Eno et Load en fonction de Q(15 downto 0)
2°) Exprimer Done en fonction de Q(15 downto 6)
3°) On vous donne la réalisation de clk1MHz
clk1MHz <= cmpt(5) when cmpt(15 downto 6) >= "0000000001"
and cmpt(15 downto 6) < "0000010000" else
'0';
N'aviez-vous pas remarqué que cette "horloge" ne fonctionne pas tout le temps, seulement après un Load ?
4°) Complétez ce qui est nécessaire pour réaliser le séquenceur complet
5°) A l'aide d'un code un peu similaire à celui ci-dessous, on vous demande d'essayer encore une fois votre code sur votre carte FPGA (sans shield)
with sel select
s <= "0001" when "00",
"0010" when "01",
"0100" when "10",
"1000" when others;
Le composant "Load_And_Send" : Exercice 4
[modifier | modifier le wikicode]On redonne ci-contre, la figure que l'on cherche à réaliser. Il ne reste plus qu'à réaliser le composant du bas (à droite) que l'on a appelé load_and_send.
- Une des entrées de ce composant est "sel" mais sur deux bits. Un code comme celui donné dans l'exercice 3 (5° question) sera nécessaire pour le transformer en sélection de 1 parmi 3.
- L'autre est naturellement la donnée qui sort du transcodeur.
Ce que l'on doit faire avec ce composant est de former un signal de 16 bits avec les deux données qui n'en font que 11, et ceci lors d'un LOad synchrone. On vous rappelle que ceci peut se faire avec le code VHDL :
if rising_edge(clk) then
if load = '1' then
s16bits <= s & "0000" & dataIn & '1';
end if;
end if;
où l'opérateur '&' en VHDL est l'opérateur de concaténation. Le '1' complètement à droite est là pour éteindre le point.
- sa troisième fonctionnalité est de réaliser un décalage vers la droite quand son entrée 'en' est à un.
Quand tout cela sera réalisé, il sera grand temps de réaliser l'ensemble complet.
Le code complet corrigé
[modifier | modifier le wikicode]Voici le code complet correspondant à la résolution de notre problème.
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_arith.all;
use IEEE.STD_LOGIC_UNSIGNED.all;
entity MultiFunc is
port (
clk_100MHz : in std_logic;
DataOut, ShiftClk, LatchClk : out std_logic
);
end MultiFunc;
architecture arch_MultiFunc of MultiFunc is
component cadenceur25bits IS
PORT(clk_100MHz : IN STD_LOGIC;
sel : out std_logic_vector(1 downto 0);
eno : out std_logic;
done : out std_logic;
clk1Mhz : out std_logic;
Load : out std_logic;
eno3Hz : OUT STD_LOGIC);
END component cadenceur25bits;
component cmpt16bits IS
PORT(clk_100MHz : IN STD_LOGIC;
en : in std_logic;
cnt : OUT STD_LOGIC_VECTOR(15 DOWNTO 0));
END component cmpt16bits;
component mux4 is port (
sel: in std_logic_vector(1 downto 0);
e : in std_logic_vector(15 downto 0);
s : out std_logic_vector(3 downto 0)
);
end component mux4;
component transcod7segs IS PORT(
e : in std_logic_vector(3 downto 0);
s7segs : out std_logic_vector(6 downto 0));
END component transcod7segs;
component load_and_send is port(
clk,load,en : in std_logic;
sel : in std_logic_vector(1 downto 0);
dataIn : in std_logic_vector(6 downto 0);
DataOut : out std_logic
);
end component load_and_send;
signal s_en3Hz, s_enoRegShift, s_Load : std_logic;
signal s_data16 : std_logic_vector(15 downto 0);
signal s_transcod : std_logic_vector(3 downto 0);
signal s_sel : std_logic_vector(1 downto 0);
signal s_7segs : std_logic_vector(6 downto 0);
begin
i1: cadenceur25bits port map (
clk_100MHz => clk_100MHz,
eno => s_enoRegShift,
sel => s_sel,
done => LatchClk,
clk1Mhz => ShiftClk,
Load => s_load,
eno3Hz => s_en3Hz);
i2: cmpt16bits port map (
clk_100MHz => clk_100MHz,
en => s_en3Hz,
cnt => s_data16);
i3: mux4 port map (
sel => s_sel,
e => s_data16,
s => s_transcod);
i4: transcod7segs port map (
e => s_transcod,
s7segs => s_7segs) ;
i5: load_and_send port map(
clk => clk_100MHz,
en => s_enoRegShift,
load => s_Load,
sel => s_sel,
dataIn => s_7segs,--"1111110",--s_7segs,
DataOut => Dataout);
end arch_MultiFunc;
-- Le séquenceur général
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
ENTITY cadenceur25bits IS
PORT(clk_100MHz : IN STD_LOGIC;
sel : out std_logic_vector(1 downto 0);
-- cmptRegAndclk : out std_logic_vector(4 downto 0);
eno : out std_logic; -- environ 1 MHz et durée faible
clk1Mhz : out std_logic; -- environ 1 MHz et rapport cyclique 0.5
Load : out std_logic;
done : out std_logic;
eno3Hz : OUT STD_LOGIC);
END cadenceur25bits;
ARCHITECTURE arch_cad OF cadenceur25bits IS
signal cmpt : std_logic_vector(24 downto 0);
BEGIN
process(clk_100MHz) begin
if rising_edge(clk_100MHz) then
cmpt <= cmpt + 1;
end if;
end process;
eno3Hz <= '1' when cmpt(24 downto 0) = "1111111111111111111111111" else
'0';
sel <= cmpt(17 downto 16);
with cmpt(15 downto 0) select
eno <= '1' when "0000000001000000",
'1' when "0000000010000000",
'1' when "0000000011000000",
'1' when "0000000100000000",
'1' when "0000000101000000",
'1' when "0000000110000000",
'1' when "0000000111000000",
'1' when "0000001000000000",
'1' when "0000001001000000",
'1' when "0000001010000000",
'1' when "0000001011000000",
'1' when "0000001100000000",
'1' when "0000001101000000",
'1' when "0000001110000000",
'1' when "0000001111000000",
-- '1' when "0000010000000000",
'0' when others;
clk1MHz <= cmpt(5) when cmpt(15 downto 6) >= "0000000001"
and cmpt(15 downto 6) < "0000010000" else
'0';
with cmpt(15 downto 0) select
Load <= '1' when "0000000000000000",
'0' when others;
with cmpt(15 downto 6) select
done <= '1' when "0000010011",
'0' when others;
END arch_cad;
-- Le compteur à afficher
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
ENTITY cmpt16bits IS
PORT(clk_100MHz : IN STD_LOGIC;
en : in std_logic;
cnt : OUT STD_LOGIC_VECTOR(15 DOWNTO 0));
END cmpt16bits;
ARCHITECTURE arch_cmpt16bits OF cmpt16bits IS
signal cmpt : std_logic_vector(15 downto 0);
BEGIN
process(clk_100MHz) begin
if rising_edge(clk_100MHz) then
if en='1' then
cmpt <= cmpt + 1;
end if;
end if;
end process;
cnt <= cmpt;
END arch_cmpt16bits;
-- description du multiplexeur
library ieee;
use ieee.std_logic_1164.all;
entity mux4 is port (
sel: in std_logic_vector(1 downto 0);
e : in std_logic_vector(15 downto 0);
s : out std_logic_vector(3 downto 0)
);
end entity mux4;
architecture behavior of mux4 is
begin
with sel select
s <= e(3 downto 0) when "00",
e(7 downto 4) when "01",
e(11 downto 8) when "10",
e(15 downto 12) when others;
end behavior;
-- le transcodeur
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
ENTITY transcod7segs IS PORT(
e : in std_logic_vector(3 downto 0);
s7segs : out std_logic_vector(6 downto 0));
END transcod7segs;
ARCHITECTURE arch of transcod7segs IS
BEGIN
with e select
--abcdefg
s7segs <= "0000001" when "0000",
"1001111" when "0001",
"0010010" when "0010",
"0000110" when "0011",
"1001100" when "0100",
"0100100" when "0101",
"0100000" when "0110",
"0001111" when "0111",
"0000000" when "1000",
"0000100" when "1001",
"0001000" when "1010",
"1100000" when "1011",
"0110001" when "1100",
"1000010" when "1101",
"0110000" when "1110",
"0111000" when others;
END;
library ieee;
use ieee.std_logic_1164.all;
entity load_and_send is port(
clk,load,en : in std_logic;
sel : in std_logic_vector(1 downto 0);
dataIn : in std_logic_vector(6 downto 0);
DataOut : out std_logic
);
end entity load_and_send;
architecture arch_load_and_send of load_and_send is
signal s : std_logic_vector(3 downto 0);
signal s16bits : std_logic_vector(15 downto 0);
signal state : std_logic_vector(4 downto 0);
begin
-- transcodage pour selection des afficheurs
with sel select
s <= "0001" when "00",
"0010" when "01",
"0100" when "10",
"1000" when others;
-- memoristion des données
process(clk) begin
if rising_edge(clk) then
if load = '1' then
s16bits <= s & "0000" & dataIn & '1';
elsif en = '1' then
s16bits <= '0' & s16bits(15 downto 1) ;
end if;
end if;
end process;
DataOut <= s16bits(0);
end arch_load_and_send;
Avec ce code source, seule la gestion du point n'est pas correcte : pas de point pour le digit des unités comme nous le désirons , mais un point sur les trois autres digits !
Comme la photo l'a montré, nous utilisons le connecteur JD d'une carte Nexyx 3. Nous donnons donc le fichier ucf pour cette carte :
## This file is a general .ucf for Nexys3 rev B board ## To use it in a project: ## - remove or comment the lines corresponding to unused pins ## - rename the used signals according to the project ## 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; ##JD, LX16 Die only #NET "JD<0>" LOC = "G11" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L40P, Sch name = JD1 #NET "JD<1>" LOC = "F10" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L40N, Sch name = JD2 #NET "JD<2>" LOC = "F11" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L42P, Sch name = JD3 #NET "JD<3>" LOC = "E11" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L42N, NET "LatchClk" LOC = "F10" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L40N, Sch name = JD2 NET "ShiftClk" LOC = "F11" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L42P, Sch name = JD3 NET "DataOut" LOC = "E11" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L42N, Sch name = JD4 #NET "JD<4>" LOC = "D12" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L47P, Sch name = JD7 #NET "JD<5>" LOC = "C12" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L47N, Sch name = JD8 #NET "JD<6>" LOC = "F12" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L51P, Sch name = JD9 #NET "JD<7>" LOC = "E12" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L51N, Sch name = JD10
Comptage décimal : Exercice 5
[modifier | modifier le wikicode]Le compatge précédent affiche en hexadécimal. Les informaticiens savent le lire mais pas le commun des mortels. On vous demande donc de remplacer le compteur sur 16 bits par quatre compteur BCD cascadés.
Le schéma de principe est présenté dans la figure ci-contre. L'idée générale est de remplacer le compteur 16 bits de la section précédente par quatre compteurs cascadés : un par digit.
Nous n'avons pas nommé les fils internes dans la figure. C'est la première étape de ce que vous ferez si vous ne voulez pas vous perdre dans le câblage des composants.
Indication : Vous disposez d'un compteur décimal BCD cascadable complet:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity CounterBCD is
port( EN: in std_logic;
Clock: in std_logic;
Reset: in std_logic;
ENO : out std_logic;
Output: out std_logic_vector(3 downto 0));
end CounterBCD;
architecture Behavioral of CounterBCD is
signal cmpt: std_logic_vector(3 downto 0);
signal s_en_cmpt: std_logic_vector(4 downto 0);
begin process(Clock,Reset)
begin
if(rising_edge(Clock)) then
if Reset='1' then
cmpt <= "0000";
elsif EN='1' then
if cmpt="1001" then
cmpt<="0000";
else
cmpt <= cmpt + 1;
end if;
end if;
end if;
end process;
Output <= cmpt;
s_en_cmpt <= en & cmpt;
with s_en_cmpt select
ENO <= '1' when "11001",
'0' when others;
end Behavioral;
Voici la solution correspondante.
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_arith.all;
use IEEE.STD_LOGIC_UNSIGNED.all;
entity MultiFunc is
port (
clk_100MHz : in std_logic;
DataOut, ShiftClk, LatchClk : out std_logic
);
end MultiFunc;
architecture arch_MultiFunc of MultiFunc is
component cadenceur25bits IS
PORT(clk_100MHz : IN STD_LOGIC;
sel : out std_logic_vector(1 downto 0);
eno : out std_logic;
done : out std_logic;
clk1Mhz : out std_logic;
Load : out std_logic;
eno3Hz : OUT STD_LOGIC);
END component cadenceur25bits;
--component cmpt16bits IS
-- PORT(clk_100MHz : IN STD_LOGIC;
-- en : in std_logic;
-- cnt : OUT STD_LOGIC_VECTOR(15 DOWNTO 0));
--END component cmpt16bits;
component CounterBCD is
port( EN: in std_logic;
Clock: in std_logic;
Reset: in std_logic;
ENO : out std_logic;
Output: out std_logic_vector(3 downto 0));
end component CounterBCD;
component mux4 is port (
sel: in std_logic_vector(1 downto 0);
e : in std_logic_vector(15 downto 0);
s : out std_logic_vector(3 downto 0)
);
end component mux4;
component transcod7segs IS PORT(
e : in std_logic_vector(3 downto 0);
s7segs : out std_logic_vector(6 downto 0));
END component transcod7segs;
component load_and_send is port(
clk,load,en : in std_logic;
sel : in std_logic_vector(1 downto 0);
dataIn : in std_logic_vector(6 downto 0);
DataOut : out std_logic
);
end component load_and_send;
signal s_en3Hz, s_enoRegShift, s_Load : std_logic;
signal s_eno, s_eno2, s_eno3 : std_logic;
signal s_data16 : std_logic_vector(15 downto 0);
signal s_transcod : std_logic_vector(3 downto 0);
signal s_sel : std_logic_vector(1 downto 0);
signal s_7segs : std_logic_vector(6 downto 0);
begin
i1: cadenceur25bits port map (
clk_100MHz => clk_100MHz,
eno => s_enoRegShift,
sel => s_sel,
done => LatchClk,
clk1Mhz => ShiftClk,
Load => s_load,
eno3Hz => s_en3Hz);
bcdUnit: CounterBCD port map(
EN => s_en3Hz,
Clock => clk_100MHz,
Reset => '0',
ENO => s_eno,
Output => s_data16(3 downto 0));
bcdDiz: CounterBCD port map(
EN => s_eno,
Clock => clk_100MHz,
Reset => '0',
ENO => s_eno2,
Output => s_data16(7 downto 4));
bcdCent: CounterBCD port map(
EN => s_eno2,
Clock => clk_100MHz,
Reset => '0',
ENO => s_eno3,
Output => s_data16(11 downto 8));
bcdMil: CounterBCD port map(
EN => s_eno3,
Clock => clk_100MHz,
Reset => '0',
ENO => open,
Output => s_data16(15 downto 12));
i3: mux4 port map (
sel => s_sel,
e => s_data16,
s => s_transcod);
i4: transcod7segs port map (
e => s_transcod,
s7segs => s_7segs) ;
i5: load_and_send port map(
clk => clk_100MHz,
en => s_enoRegShift,
load => s_Load,
sel => s_sel,
dataIn => s_7segs,--"1111110",--s_7segs,
DataOut => Dataout);
end arch_MultiFunc;
Réalisation d'un réveil : Exercice 6
[modifier | modifier le wikicode]Transformer l'ensemble des quatre compteurs décimaux en un ensemble capable de compter des heures et minutes.
La résolution de ce problème nécessitera d'utiliser
- le reset pour les compteurs dizaine et unité des heures : détection de 23 et du en sur les unités passe à 00 à l'aide du reset (bouclage synchrone par du combinatoire, synchrone car le reset est synchrone)
- pour les minutes, le comptage des dizaine nécessitera la réalisation d'un compteur modulo 6. Il s'agit de partir du compteur BCD de la section précédente et d'exécuter une simple modification.
L'ensemble du travail à réaliser vous est présenté dans la figure ci-contre. Cet ensemble remplacera le compteur 16 bits qui attaquait le multiplexeur. Nous gardons donc la fréquence de 3 Hz pour ne pas attendre trop longtemps le passage de 23:59 à 00:00 qui est la difficulté de cet exercice.
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_arith.all;
use IEEE.STD_LOGIC_UNSIGNED.all;
entity MultiFunc is
port (
clk_100MHz : in std_logic;
DataOut, ShiftClk, LatchClk : out std_logic
);
end MultiFunc;
architecture arch_MultiFunc of MultiFunc is
component cadenceur25bits IS
PORT(clk_100MHz : IN STD_LOGIC;
sel : out std_logic_vector(1 downto 0);
eno : out std_logic;
done : out std_logic;
clk1Mhz : out std_logic;
Load : out std_logic;
eno3Hz : OUT STD_LOGIC);
END component cadenceur25bits;
--component cmpt16bits IS
-- PORT(clk_100MHz : IN STD_LOGIC;
-- en : in std_logic;
-- cnt : OUT STD_LOGIC_VECTOR(15 DOWNTO 0));
--END component cmpt16bits;
component CounterBCD is
port( EN: in std_logic;
Clock: in std_logic;
Reset: in std_logic;
ENO : out std_logic;
Output: out std_logic_vector(3 downto 0));
end component CounterBCD;
component CounterModulo6 is -- pour dizaines minutes
port( EN: in std_logic;
Clock: in std_logic;
Reset: in std_logic;
ENO : out std_logic;
Output: out std_logic_vector(3 downto 0));
end component CounterModulo6;
component mux4 is port (
sel: in std_logic_vector(1 downto 0);
e : in std_logic_vector(15 downto 0);
s : out std_logic_vector(3 downto 0)
);
end component mux4;
component transcod7segs IS PORT(
e : in std_logic_vector(3 downto 0);
s7segs : out std_logic_vector(6 downto 0));
END component transcod7segs;
component load_and_send is port(
clk,load,en : in std_logic;
sel : in std_logic_vector(1 downto 0);
dataIn : in std_logic_vector(6 downto 0);
DataOut : out std_logic
);
end component load_and_send;
signal s_en3Hz, s_enoRegShift, s_Load : std_logic;
signal s_eno, s_eno2, s_eno3 : std_logic;
signal s_eno2_heure : std_logic_vector(8 downto 0);
signal s_reset : std_logic; -- pour les heures
signal s_data16 : std_logic_vector(15 downto 0);
signal s_transcod : std_logic_vector(3 downto 0);
signal s_sel : std_logic_vector(1 downto 0);
signal s_7segs : std_logic_vector(6 downto 0);
begin
i1: cadenceur25bits port map (
clk_100MHz => clk_100MHz,
eno => s_enoRegShift,
sel => s_sel,
done => LatchClk,
clk1Mhz => ShiftClk,
Load => s_load,
eno3Hz => s_en3Hz);
bcdUnit: CounterBCD port map(
EN => s_en3Hz,
Clock => clk_100MHz,
Reset => '0',
ENO => s_eno,
Output => s_data16(3 downto 0));
bcdDiz: CounterModulo6 port map(
EN => s_eno,
Clock => clk_100MHz,
Reset => '0',
ENO => s_eno2,
Output => s_data16(7 downto 4));
bcdCent: CounterBCD port map(
EN => s_eno2,
Clock => clk_100MHz,
Reset => s_reset,
ENO => s_eno3,
Output => s_data16(11 downto 8));
bcdMil: CounterBCD port map(
EN => s_eno3,
Clock => clk_100MHz,
Reset => s_reset,
ENO => open,
Output => s_data16(15 downto 12));
s_eno2_heure <= s_eno2 & s_data16(15 downto 8);
with s_eno2_heure select
s_reset <= '1' when "100100011",
'0' when others;
i3: mux4 port map (
sel => s_sel,
e => s_data16,
s => s_transcod);
i4: transcod7segs port map (
e => s_transcod,
s7segs => s_7segs) ;
i5: load_and_send port map(
clk => clk_100MHz,
en => s_enoRegShift,
load => s_Load,
sel => s_sel,
dataIn => s_7segs,--"1111110",--s_7segs,
DataOut => Dataout);
end arch_MultiFunc;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
-- pour les dizaines de minutes
entity CounterModulo6 is
port( EN: in std_logic;
Clock: in std_logic;
Reset: in std_logic;
ENO : out std_logic;
Output: out std_logic_vector(3 downto 0));
end CounterModulo6;
architecture Behavioral of CounterModulo6 is
signal cmpt: std_logic_vector(3 downto 0);
signal s_en_cmpt: std_logic_vector(4 downto 0);
begin process(Clock,Reset)
begin
if(rising_edge(Clock)) then
if Reset='1' then
cmpt <= "0000";
elsif EN='1' then
if cmpt="0101" then
cmpt<="0000";
else
cmpt <= cmpt + 1;
end if;
end if;
end if;
end process;
Output <= cmpt;
s_en_cmpt <= en & cmpt;
with s_en_cmpt select
ENO <= '1' when "10101",
'0' when others;
end Behavioral;
Et si l'on mettait un processeur pour piloter tout cela
[modifier | modifier le wikicode]Maintenant que nous avons réalisé un périphérique fonctionnel, il est grand temps de le piloter par un processeur. Ce travail va consister donc à remplacer le compteur 16 bits de l'exercice 4 par deux ports d'un processeur. Nous allons utiliser pour cela l'ATMega16 déjà décrit dans ce livre.
Exercice 7 : Réalisation simple avec deux PORTs
[modifier | modifier le wikicode]L'objectif de cet exercice est d'arriver au même point que l'exercice 6 mais avec un processeur. Il s'agit donc de faire défiler des chiffres en format hh:mm, deux digits pour les heures et deux pour les minutes. Le transcodage matériel est gardé, cela implique que les données seront sur 16 bits. Pour simplifier le dessin de la figure, un certain nombre de détails n'ont pas été présentés :
- le fichier io.vhd n'est pas dessiné mais une partie de son contenu seulement (le process iowr qui est le seul qui nous intéresse)
- le fichier complet du processeur qui est le fichier complet contenu dans le FPGA n'est que très partiellement présenté
Malgré cette représentation partielle par le dessin, nous espérons que le travail à réaliser vous paraîtra clair :
- retirer le compteur 16 bits de l'exercice 6 et de le remplacer par une entrée sur 16 bits
- réaliser l'entrée 16 bits avec deux PORTs du processeur. On prendra PORTB pour les 8 bits de poids faible, donc les minutes et PORTC pour les 8 bits de poids fort, donc les heures
- le composant top de l'exercice 6 amputé de son compteur 16 bits, sera ensuite changé en composant de "io.vhd" et câblé correctement. Ses trois sorties seront amenées jusqu'aux sorties du processeur
Ce câblage utilisant deux PORTs 8 bits pour faire la donnée sur 16 bits qui doit être affichée sur les 4 digits contraint l'arithmétique que l'on doit utiliser.
- Ainsi 0x0635 doit afficher 06:35 sur les quatre digits (les deux points ont été rajoutés pour les explications mais ne sont pas gérés et d'ailleurs pas présents sur le shield).
- 0x1129 qui représente 11:29 doit donner 11:30 pour l'ajout de 1 alors que 0x1129 + 1 = 0x112A ! Que se passe-t-il si vous ajoutez 0x0006 à 0x112A ? Est-ce général ?
- 0x1459 qui représente 14:59 à qui on ajoute 1 doit donner 15:00 soit 0x1500. Or si vous ajoutez 1 à 0x1459 cela donne 0x145A ! Que se passe-t-il si vous ajoutez 0x0006 à 0x112A puis 0x00A0 ?
- 0x2359 qui représente 23:59 doit donner 00:00 si on ajoute 1 (soit 0x0000) mais 0x2359+1 = 0x295A !
Réaliser ensuite le programme qui compte comme un réveil. 0n vous conseille cependant d'utiliser l'incrémentation ++, habituelle en C, et de la faire suivre d'une série de tests.
#include <avr/io.h>
#include <avr/interrupt.h>
#undef F_CPU
#define F_CPU 50000000UL
#include "util/delay.h"
int main(int argc, char * argv[])
{ uint16_t hh_mm = 0; // pour les heures et minutes
for (;;) {
PORTB = hh_mm;
PORTC = (hh_mm>>8);
PORTD = hh_mm;
hh_mm++;
if ((hh_mm & 0x000F) > 0x0009)
hh_mm += 0x0006;
if ((hh_mm & 0x00F0) > 0x0050)
hh_mm += 0x00A0;
if ((hh_mm & 0x0F00) > 0x0900)
hh_mm += 0x0600;
if ((hh_mm & 0xFF00) > 0x2300)
hh_mm = 0x0000;
_delay_ms(300);
}
}
La série de "if" qui suit l'incrémentation n'est pas facile à trouver intuitivement. Elle est liée au fait que l'on veut garder les données par paquets de 4 bits et en décimal. Et que ces données doivent respecter l'arithmétique des des minutes et heures (qui n'est pas habituelle). L'ordre de ces "if" est absolument impératif.
Exercice 8 : Réalisation d'un réveil complet
[modifier | modifier le wikicode]Nous allons utiliser les ressources complètes du shield multifonction pour réaliser un réveil complet. Il s'agit d'utiliser en plus des afficheurs, les trois boutons et le buzzer. Il doit être possible de régler l'heure de réveil et l'armement de ce dernier. L'heure courante sera gardée assez rapide pour les tests.
Le cahier des charges est le suivant :
- le bouton de gauche est utilisé pour l'armement du réveil et allume une LED.
- le bouton du milieu est utilisé pour passer en mode réglage (incrémentation) de l'heure de réveil. Un nouvel appui passe en mode décrémentation. Enfin un nouvel appui quitte le mode réglage
- le bouton de droite incrémente l'heure de réveil. Un bon fonctionnement serait d'augmenter la vitesse de défilement de ces heures en fonction du temps d'appui
La sérigraphie du shield vous donne :
- bouton gauche en A1, bouton milieu en A2 et bouton droite en A3
- LED D1 en 13. En fait les 4 LEDs font face à leur connecteurs
- Le buzzer est en (-3)
Ces informations sont importantes pour câbler votre shield au FPGA.
Voir aussi
[modifier | modifier le wikicode]- Serie de TPs : T1c, TP2c, ... et TP6c autour d'un réveil sur carte Altera avec affichage LCD
- librairie github pour le shield multifonction
- exemples de code Arduino
Adapter des shields 5V aux cartes FPGA 3,3 V
[modifier | modifier le wikicode]Nous avons déjà eu l'occasion de le répéter dans ce livre, les cartes Digilent sont conçues de plus en plus pour n'avoir comme connecteur d'extension que des PMod. C'est pratique si l'on achète des extensions PMod. Nous en utilisons quelques unes avec succès (voir chapitre sur la robotique mobile) mais nous les trouvons bien trop chères. Un sonar se retrouve à 40 € alors que le module HC SR04 peut être trouvé aux alentours de 2 €. Un PMod double afficheur 7 segments coûte 16 € tandis qu'on a utilisé des quadruples afficheurs pour moins de 4 €. Bien sûr le branchement n'est alors pas immédiat. Nous ne prétendons pas que les qualités sont les mêmes mais pour les utiliser avec des étudiants la basse qualité suffit bien souvent. L'autre problème important est qu'il n'y a pas de 5V sur le PMod !
Digilent va garder ses PMod certainement longtemps... mais commence à faire des cartes avec des connecteurs de type Arduino comme la carte Arty. Mais là c'est le prix de la carte elle-même qui pose problème ! Elle est à 99,00$ aux US, mais se retrouve à 174 € chez nous !
Tous ces discours n'amènent qu'à une question : comment faire pour utiliser les shields 5V avec les cartes FPGA ?
Les réponses sont encore chinoises :
- vous pouvez trouver un module d'alimentation 5V et 3,3 V pour plaque à essais pour un peu plus de 2 €. Voici un lien qui montre de quoi nous parlons.
- si vous avez besoin d'adapter les tensions, des modules convertisseur de tension logique bidirectionnel peuvent être trouvés aux alentours d'1 €.
Soyons un peu plus précis. Dans le cas général, au-delà des problèmes de PMod, vous allez vous trouver dans deux situations différentes :
- l'i2c, abordé dans la section suivante. C'est un problème à part car ce qui caractérise l'i2c c'est qu'il nécessite des résistances de tirage (pull-up). L'i2c 5V aura donc des résistances de tirages reliées à 5V. Si votre maître est en i2c 3,3 V vous aurez votre maître qui sera tiré à 5V dès qu'il recevra une réponse du périphérique... et il ne supporte pas forcément. Il vous faut alors les adaptations bidirectionnelles évoquées.
- tous les autres protocoles comme le SPI, la rs232 et d'autres ne nécessitent pas forcément une adaptation bidirectionnelle. Si un périphérique 5V est commandé par un signal 3,3 V cela ne pose aucun problème en général. Si ce périphérique 5V envoi un 1 logique au maître 3,3 V, il faut adapter. Cela peut se faire avec une simple paire de résistance 1k, 2k ou même 10k, 20k.
Une remarque pour terminer. Quand nous disons que le 3,3 V peut commander sans problème du 5V, ceci est vrai pour du TTL. Nous allons rencontrer dans la suite de ce chapitre deux circuits qui ne vérifient pas les conditions :
- TM1637 pour commander 4 digits de 7 segments alimenté en 5V mais qui nécessite 0,7*Vcc soit 3,5 V pour fonctionner correctement
- L298N pour commander deux moteurs à courant continu qui nécessite une adaptation de tension au moins pour une question de sûreté de fonctionnement
De l'I2C pour accéder à d'autres capteurs
[modifier | modifier le wikicode]Nous avons déjà eu l'occasion d'aborder l'i2c dans ce livre, mais dans un contexte précis : Etude de la manette Nunchuk. Nous avions réalisé un périphérique complètement adapté à la manette. Il a cependant été réalisé avec une grosse machine d'états et il est temps pour nous de réaliser un périphérique à peu près compatible avec les périphériques i2c que l'on trouve dans les AVR. L'intérêt est alors sa facilité de mise en œuvre par programme.
Conception du périphérique i2c
[modifier | modifier le wikicode]La conception d'un périphérique i2c est trop ardue pour en faire un TP de niveau 15. Nous avons décidé de la déplacer dans la partie cours de ce livre, dans le chapitre Améliorer l'ATMega8 avec l'ATMega16 et l'ATMega32. Ce cours a été rédigé sous forme d'exercices et nous invitons les architectes matériels en herbe à les réaliser, voir à les améliorer.
Nous allons garder dans ce chapitre de TP les utilisations pratiques de ce périphérique.
Périphérique Tiny RTC
[modifier | modifier le wikicode]C'est un composant dont le prix varie beaucoup : nous l'avons acheté pour environ de 1,1 € et peut être encore trouvé à ce prix. De toute façon, pas de quoi faire un crédit sur 20 ans.
Ce composant est décrit dans son propre WIKI. Vous y trouverez une librairie et un programme d'exemple suffisant pour se lancer.
Le problème pour ce composant avec un FPGA est encore une fois électrique. Le circuit central, le DS1307 possède deux entrées de tension :
- une entrée batterie avec une tension entre 2,0 V et 3,5 V (peut laisser présager une compatibilité avec le FPGA mais ...)
- une entrée avec une tension entre 4,5 V et 5,5 V pour l'interface i2c, et là c'est le drame...
Il est donc impossible de brancher le Tiny RTC directement sur un FPGA : une adaptation en tension est nécessaire.
Périphérique MPU6050 sur carte Nexys 3
[modifier | modifier le wikicode]Il s'agit d'un accéléromètre doublé d'un gyroscope. Il peut être trouvé aux environs de 2 € et quand on sait qu'il possède un processeur 32 bits spécialisé pour les mouvements (Digital Motion Processor : DMP), il est facile de se laisser tenter. Signalons aussi qu'un chapitre de TP est consacré à ce composant dans le livre sur les AVR et qu'un peu de documentation peut être trouvée plus loin dans ce chapitre.
Utilisation en 3,3 V avec un microcontrôleur
[modifier | modifier le wikicode]Comme nous en avons pris l'habitude dans ce chapitre, nous commençons par essayer les shields avec un processeur, en général 32 bits, qui a l'avantage de fonctionner en 3,3 V. Nous ne dérogerons pas à ce rituel ici.
Pour ne pas utiliser toujours un MSP432, nous avons décidé d'utiliser une carte launchpad tm4c123 de chez Texas Instrument. Elle sera essayée avec l'environnement de programmation Energia tellement proche de l'Arduino qu'un Copier-Coller du code est suffisant.
Voici donc notre code de départ. Il se cantonne à relever les données de l'accéléromètre, pas celles du gyroscope et de les envoyer à travers la liaison série.
// MPU-6050 Short Example Sketch
// By Arduino User JohnChi
// August 17, 2014
// Public Domain
#include<Wire.h>
const int MPU=0x68; // I2C address of the MPU-6050
int16_t AcX,AcY,AcZ;
void setup(){
Wire.begin();
Wire.beginTransmission(MPU);
Wire.write(0x6B); // PWR_MGMT_1 register
Wire.write(0); // set to zero (wakes up the MPU-6050)
Wire.endTransmission(true);
Serial.begin(9600);
}
void loop(){
Wire.beginTransmission(MPU);
Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H)
Wire.endTransmission(false);
Wire.requestFrom(MPU,6,true); // request a total of 6 registers
AcX=Wire.read()<<8|Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
AcY=Wire.read()<<8|Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
AcZ=Wire.read()<<8|Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
Serial.print("AcX = "); Serial.print(AcX);
Serial.print(" | AcY = "); Serial.print(AcY);
Serial.print(" | AcZ = "); Serial.println(AcZ);
delay(333);
}
Qui fonctionne correctement à part quelque fois des petits problèmes avec la liaison série qui n'ont donc rien à voir avec ce que l'on cherche à résoudre. Le MPU6050 fonctionne donc parfaitement lorsqu'il est alimenté en 3,3 V ou en 5V.
Utilisation avec un FPGA
[modifier | modifier le wikicode]Le périphérique pour la Nunchuk dans un autre chapitre est utilisable tel quel. Par contre le programme doit être légèrement modifié. La raison en est que le prorocole i2c utilisé est différent. Je ne parle pas du protocole i2c physique, qui bien sûr, reste identique pour les deux périphériques... mais de la façon d'interroger le périphérique pour lui demander des données.
L'adresse du périphérique MPU6050 peut être 0x68 ou 0x69 en fonction de la valeur sur l'entrée AD0 du circuit. Nous prendrons AD0 relié à la masse ce qui fixe l'adresse à 0x68.
Le code commence bien sûr par une initialisation de l'i2c physique puis se poursuit l'envoi de 0x6B suivi de 0x00. Ceci termine la partie setup
Ensuite vient la boucle infinie qui réinitialise la lecture par l'envoi de 0x3B puis d'une lecture des six données. Comme d'habitude en i2c la fin de lecture se termine par un non acquittement suivi d'un stop.
Voici donc ce code complet. Nous y avons laissé les sous-programmes pour la liaison série même si nous ne les utilisons pas.
#include <avr/io.h>
//#include <avr/interrupt.h>
#undef F_CPU
#define F_CPU 50000000UL
#include "util/delay.h"
#define TWWR 1
#define TWRD 3
#define MPU 0x68
//******* prototypes i2c
void TWIInit(void);
void TWIWrite(uint8_t u8data);
void TWIWriteStop(uint8_t u8data);
void TWIStartWrite(uint8_t u8data);
void TWIStartWriteStop(uint8_t u8data);
void TWIStop();
//read byte with ACK
uint8_t TWIReadACK(void);
//read byte with ACK and STOP
uint8_t TWIReadACKStop(void);
//read byte with NACK and STOP
uint8_t TWIReadNACKStop(void);
// RS232
void usart_init(void);
void usart_send(unsigned char ch);
char usart_receive(void);
void usart_puts(char str[]);
void usart_puts_hexa(int nbQ3_13);
int main() {
uint8_t i, t[6];
// init
TWIInit();
_delay_us(500);
TWIStartWrite(MPU<<1);
TWIWrite(0x6B);
TWIWriteStop(0x00);
_delay_ms(10);
// loop
while(1) {
// reinitialisation pour lecture
TWIStartWrite(MPU<<1);
TWIWriteStop(0x3B);
_delay_ms(1);
TWIStartWrite((MPU<<1)|1);
for (i=0;i<5;i++)
t[i] = TWIReadACK();
t[5] = TWIReadNACKStop();
PORTC = t[0];
_delay_ms(100);
}
return 0;
}
void TWIInit(void)
{
//set SCL to 100kHz
TWBR = 100;
//enable TWI,
TWCR = (1<<TWEN);
}
void TWIWrite(uint8_t u8data)
{
TWDR = u8data;
TWCR = (1<<TWINT)|(1<<TWWR)|(1<<TWEN);
while ((TWCR & (1<<TWINT)) == 0);
}
void TWIWriteStop(uint8_t u8data)
{
TWDR = u8data;
TWCR = (1<<TWINT)|(1<<TWWR)|(1<<TWSTO)|(1<<TWEN);
while ((TWCR & (1<<TWINT)) == 0);
}
void TWIStartWrite(uint8_t u8data)
{
TWDR = u8data;
TWCR = (1<<TWINT)|(1<<TWWR)|(1<<TWSTA)|(1<<TWEN);
while ((TWCR & (1<<TWINT)) == 0);
}
void TWIStartWriteStop(uint8_t u8data)
{
TWDR = u8data;
TWCR = (1<<TWINT)|(1<<TWWR)|(1<<TWSTO)|(1<<TWSTA)|(1<<TWEN);
while ((TWCR & (1<<TWINT)) == 0);
}
//read byte with ACK
uint8_t TWIReadACK(void)
{
TWCR = (1<<TWINT)|(1<<TWEA)|(1<<TWRD)|(1<<TWEN);
while ((TWCR & (1<<TWINT)) == 0);
return TWDR;
}
//read byte with ACK and STOP
uint8_t TWIReadACKStop(void)
{
TWCR = (1<<TWINT)|(1<<TWEA)|(1<<TWRD)|(1<<TWSTO)|(1<<TWEN);
while ((TWCR & (1<<TWINT)) == 0);
return TWDR;
}
//read byte with NACK and STOP
uint8_t TWIReadNACKStop(void)
{
TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWRD)|(1<<TWEN);
while ((TWCR & (1<<TWINT)) == 0);
return TWDR;
}
//************************************************************************
// function usart_init()
// purpose: init first rs232 PORT
// arguments:
// no argument
// return:
// note: 38400,8,n,2 hard coded : transmission and reception
//************************************************************************
void usart_init(void) {
UCSRB = (1<<TXEN)|((1<<RXEN)); // transmission et reception
}
//************************************************************************
// function uart_send()
// purpose: put character in first rs232 PORT
// arguments:
// corresponding character
// return:
// note: 38400,8,n,2 hard coded
// initialisation uart prealable requise
//************************************************************************
void usart_send(unsigned char ch){
while(!(UCSRA & (1<<UDRE)));
UDR = ch;
}
//************************************************************************
// function uart_receive()
// purpose: read character in second rs232 PORT
// arguments:
// corresponding character
// return: non-blocking sub return 1 if no data present else return char
// note: 38400,8,n,2 hard coded, non-blocking sub return 0 if no data present
// initialisation uart prealable requise
//************************************************************************
char usart_receive(void){
while (!(UCSRA & (1<<RXC))); //attente tant que Data Present en réception
return UDR;
}
//************************************************************************
// function usart_puts()
// purpose: puts characters in first rs232 PORT
// arguments:
// corresponding string
// return:
// note: 38400,8,n,2 hard coded : transmission
// initialisation uart prealable requise
//************************************************************************
void usart_puts(char str[]){
uint8_t i=0;
do {
usart_send(str[i]);
i++;
} while(str[i]!=0);
}
//************************************************************************
// function usart_puts_hexa()
// purpose: puts number in hexadecimel in first rs232 PORT
// arguments:
// corresponding number
// return:
// note: 38400,8,n,2 hard coded : transmission
// initialisation uart prealable requise
// only for 16-bit numbers and then Q3.13 numbers
//************************************************************************
void usart_puts_hexa(int nbQ3_13){
int8_t i=0,digit=0;
char char_digit;
usart_send('0');usart_send('X');
for (i=12;i>-1;i-=4) {// only four digits
digit = (nbQ3_13 >> i) & 0x0F;
char_digit=digit+0x30;
if (char_digit>0x39) char_digit += 7;
usart_send(char_digit);
}
}
Vous pouvez remarquer le "PORTC=t[0] qui permet de sortir la première valeur lue sur les leds. C'est le poids fort de l'accélération suivant l'axe des x. Un bon fonctionnement doit donc se traduire par des leds qui changent quand l'orientation de l'accéléromètre varie car celui-ci est sensible à l'accélération de pesanteur.
Périphérique MPU6050 sur carte Altera DE2-115
[modifier | modifier le wikicode]A priori le portage n'est qu'électrique : on débranche la nexys 3 et on branche la DE2-115. Mais, comme toujours, il faut se méfier des à priori...
Rappelons pour ceux qui arrivent ici sans avoir lu grand chose des autres parties de ce livre, que nous n'avons pas encore porté notre processeur préféré (ATMega16) sur les cyclones d'Altera mais que nous devons nous contenter du modeste ATTiny861. C'est mieux que rien et cela se programme en C.
Pour commander notre cœur i2c, nous avons décidé d'utiliser les registres du Tiny861 et non ceux des ATMega : ceux des ATMegas ont un nom en TWXX tandis que ceux du Tiny861 ont un nom en USIXX. Pourquoi procéder comme cela ?
- avantage : on évite les redéfinitions des noms de registres dans la partie entête du programme c
- inconvénient : on s'éloigne du cœur i2c du vrai Tiny861 de chez Atmel car si les noms ont changés, il en est de même des fonctionnalités
L'inconvénient est facile à lever : il suffit d'écrire une librairie pour l'i2c... que voici
#define TWWR 1
#define TWEN 2
#define TWRD 3
#define TWSTO 4
#define TWSTA 5
#define TWEA 6
#define TWINT 7
#define WII_NUNCHUK_I2C_ADDRESS 0x52
void TWIInit(void)
{
//set SCL to 100kHz
USIBR = 100;
//enable TWI,
USICR = (1<<TWEN);
}
void TWIWrite(uint8_t u8data)
{
USIDR = u8data;
USICR = (1<<TWINT)|(1<<TWWR)|(1<<TWEN);
while ((USICR & (1<<TWINT)) == 0);
}
void TWIWriteStop(uint8_t u8data)
{
USIDR = u8data;
USICR = (1<<TWINT)|(1<<TWWR)|(1<<TWSTO)|(1<<TWEN);
while ((USICR & (1<<TWINT)) == 0);
}
// ne fonctionne pas !!
void TWIStop() {
USICR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);
while ((USICR & (1<<TWINT)) == 0);
}
void TWIStartWrite(uint8_t u8data)
{
USIDR = u8data;
USICR = (1<<TWINT)|(1<<TWWR)|(1<<TWSTA)|(1<<TWEN);
while ((USICR & (1<<TWINT)) == 0);
}
void TWIStartWriteStop(uint8_t u8data)
{
USIDR = u8data;
USICR = (1<<TWINT)|(1<<TWWR)|(1<<TWSTO)|(1<<TWSTA)|(1<<TWEN);
while ((USICR & (1<<TWINT)) == 0);
}
//read byte with ACK
uint8_t TWIReadACK(void)
{
USICR = (1<<TWINT)|(1<<TWEA)|(1<<TWRD)|(1<<TWEN);
while ((USICR & (1<<TWINT)) == 0);
return USIDR;
}
//read byte with ACK and STOP
uint8_t TWIReadACKStop(void)
{
USICR = (1<<TWINT)|(1<<TWEA)|(1<<TWRD)|(1<<TWSTO)|(1<<TWEN);
while ((USICR & (1<<TWINT)) == 0);
return USIDR;
}
//read byte with NACK and STOP
uint8_t TWIReadNACKStop(void)
{
USICR = (1<<TWINT)|(1<<TWSTO)|(1<<TWRD)|(1<<TWEN);
while ((USICR & (1<<TWINT)) == 0);
return USIDR;
}
La librairie fournie ci-dessus ne fonctionne pas dans un ATTiny861 du commerce. |
Réalisation matérielle
[modifier | modifier le wikicode]Nous vous proposons sans détailler la ressource complète sur mon site personnel. Comme d'habitude le sous-répertoire soft contient le programme c et sa compilation.
Voir aussi
[modifier | modifier le wikicode]Module d'affichage LED avec affichage de l'horloge pour Arduino
[modifier | modifier le wikicode]Ce module n'est pas à proprement parler un shield. Si vous voulez voir son aspect tapez le titre de cette section dans Google ou autre. Ce qui nous intéresse est son prix (3,84 € au moment de l'écriture de cette section 2017) et que sa conception est complètement différente du shield multi fonctions de la section précédente. Une des conséquences est qu'il nous faudra modifier notre VHDL pour l'utiliser. Sa mise en œuvre nécessite de résoudre deux problèmes :
- est-il compatible avec le 3,3 V des FPGA ?
- maîtriser le circuit TM1637 de Titan Micro electronics qui équipe ce module d'affichage
Compatibilité électrique
[modifier | modifier le wikicode]La documentation du TM1637 affirme que ce composant peut être alimenté entre 4,5 V et 5,5 V. Sachant que VIH est à 0,7 VCC, il ne pourra pas être confortablement utilisé en 3,3 V. Nous l'avons appris à nos dépens.
Programmation du TM1637
[modifier | modifier le wikicode]Du code pour Arduino est facilement trouvable dans GitHub. Pour entrer dans le monde du 3,3 V, nous allons utiliser ce code avec Energia avec comme cible, une carte MSP432. Pour information Energia est l'environnement compatible Arduino pour les processeurs Texas Instrument. Nous utilisons une carte MSP432 parce que le 5V nécessaire au bon fonctionnement du module d'affichage y est présent.
Decompactez la librairie précédente dans le répertoire libraries de l'installation d'Energia. Nous n'avons pas pris le risque de garder le tiret dans le nom de celle-ci et l'avons remplacé par un caractère de soulignement. Ce travail se fait donc exactement de la même manière que pour l'environnement Arduino.
Vous avez alors à disposition un exemple que nous reproduisons :
#include <Arduino.h>
#include <TM1637Display.h>
// Module connection pins (Digital Pins)
#define CLK 2
#define DIO 3
// 2 correspond à P6.0
// 3 correspond à P3.2
// The amount of time (in milliseconds) between tests
#define TEST_DELAY 2000
const uint8_t SEG_DONE[] = {
SEG_B | SEG_C | SEG_D | SEG_E | SEG_G, // d
SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, // O
SEG_C | SEG_E | SEG_G, // n
SEG_A | SEG_D | SEG_E | SEG_F | SEG_G // E
};
TM1637Display display(CLK, DIO);
void setup()
{
}
void loop()
{
int k;
uint8_t data[] = { 0xff, 0xff, 0xff, 0xff };
display.setBrightness(0x0f);
// All segments on
display.setSegments(data);
delay(TEST_DELAY);
// Selectively set different digits
data[0] = 0b01001001;
data[1] = display.encodeDigit(1);
data[2] = display.encodeDigit(2);
data[3] = display.encodeDigit(3);
for(k = 3; k >= 0; k--) {
display.setSegments(data, 1, k);
delay(TEST_DELAY);
}
display.setSegments(data+2, 2, 2);
delay(TEST_DELAY);
display.setSegments(data+2, 2, 1);
delay(TEST_DELAY);
display.setSegments(data+1, 3, 1);
delay(TEST_DELAY);
// Show decimal numbers with/without leading zeros
bool lz = false;
for (uint8_t z = 0; z < 2; z++) {
for(k = 0; k < 10000; k += k*4 + 7) {
display.showNumberDec(k, lz);
delay(TEST_DELAY);
}
lz = true;
}
// Show decimal number whose length is smaller than 4
for(k = 0; k < 4; k++)
data[k] = 0;
display.setSegments(data);
// Run through all the dots
for(k=0; k <= 4; k++) {
display.showNumberDecEx(0, (0x80 >> k), true);
delay(TEST_DELAY);
}
display.showNumberDec(153, false, 3, 1);
delay(TEST_DELAY);
display.showNumberDec(22, false, 2, 2);
delay(TEST_DELAY);
display.showNumberDec(0, true, 1, 3);
delay(TEST_DELAY);
display.showNumberDec(0, true, 1, 2);
delay(TEST_DELAY);
display.showNumberDec(0, true, 1, 1);
delay(TEST_DELAY);
display.showNumberDec(0, true, 1, 0);
delay(TEST_DELAY);
// Brightness Test
for(k = 0; k < 4; k++)
data[k] = 0xff;
for(k = 0; k < 7; k++) {
display.setBrightness(k);
display.setSegments(data);
delay(TEST_DELAY);
}
// On/Off test
for(k = 0; k < 4; k++) {
display.setBrightness(7, false); // Turn off
display.setSegments(data);
delay(TEST_DELAY);
display.setBrightness(7, true); // Turn on
display.setSegments(data);
delay(TEST_DELAY);
}
// Done!
display.setSegments(SEG_DONE);
while(1);
}
qui fonctionne parfaitement avec une alimentation en 5V.
Ceci n'est pas conforme à la documentation. Une autre manière de dire les choses c'est que ce jour d'essai, nous avons eu de la chance car la documentation indique clairement qu'il faut utiliser une adaptation de tension : VIH= 0,7 x Vcc vaut 3,5 V.
Réalisation matérielle
[modifier | modifier le wikicode]La communication se faisant avec un protocole de type i2c (ce n'est pas de l'i2c car il n'y a pas d'adresse) les données sont bidirectionnelles. Ceci est réalisé traditionnellement par des résistances de pull-up. Celles que l'on peut utiliser avec les contraintes dans les FPGA sont en général considérées comme trop grandes. Nous avons eu l'occasion de les utiliser avec la manette Nunchuk et un FPGA Xilinx sans problème réel de fonctionnement. D'autre part, nous avons fait fonctionner de l'i2c sans contrainte de pull-up dans les FPGA Altera.
En somme, il semble qu'il n'y a pas lieu de déclarer des entrées/sorties bidirectionnelles comme pull-up. Pour ceux qui désirent le faire sur Altera, Voir comment utiliser le "Pin Planer" ou l' "Assignement Editor" pour cela.
Programme d'essai
[modifier | modifier le wikicode]Nous sommes restés bloqués sur ce code un certain temps, jusqu'à ce que nous lisions la documentation plus en détail et comprendre qu'en principe ce composant doit être alimenté en 5V mais n'est alors pas compatible avec le 3,3 V (il faut au minimum 3,5 V).
Une fois l'adaptation réalisée, vous remarquerez assez vite que ce qui s'affiche est un peu n'importe quoi. Ceci est lié au fait que notre cœur I2C envoie le poids fort en premier alors que ce composant attend le poids faible en premier. Pour remédier à cela, il suffit de changer le contenu du tableau digitToSegment[].
#define TM1637_I2C_COMM1 0x40
#define TM1637_I2C_COMM2 0xC0
#define TM1637_I2C_COMM3 0x80
const uint8_t digitToSegment[] = {
// XGFEDCBA
0b00111111, // 0
0b00000110, // 1
0b01011011, // 2
0b01001111, // 3
0b01100110, // 4
0b01101101, // 5
0b01111101, // 6
0b00000111, // 7
0b01111111, // 8
0b01101111, // 9
0b01110111, // A
0b01111100, // b
0b00111001, // C
0b01011110, // d
0b01111001, // E
0b01110001 // F
};
int main() {
uint8_t i, t[4];
uint16_t cmpt=0;
// init
TWIInit();
_delay_ms(10);
// loop
while(1) {
t[0] = digitToSegment[cmpt & 0x000F];
t[1] = digitToSegment[(cmpt & 0x00F0)>>4];
t[2] = digitToSegment[(cmpt & 0x0F00)>>8];
t[3] = digitToSegment[(cmpt & 0xF000)>>12];
// reinitialisation pour ecriture
TWIStartWriteStop(TM1637_I2C_COMM1);
TWIStartWrite(TM1637_I2C_COMM2);
for (i=0;i<3;i++)
TWIWrite(t[i]);
TWIWriteStop(t[3]);
TWIStartWriteStop(TM1637_I2C_COMM3 | 0x0F);
PORTC=cmpt;
_delay_ms(500);
cmpt++;
}
return 0;
}
Les sous-programmes manquants sont présentés dans la partie i2c de ce chapitre.
Plutôt que de rendre ce programme complètement fonctionnel, nous préférons nous intéresser à une réalisation purement VHDL intégrée dans notre processeur.
Autre réalisation matérielle
[modifier | modifier le wikicode]Nous avons décidé de nous intéresser à ce qui a été fait comme réalisation matérielle par d'autres auteurs. github tm1637 pour FPGA (VHDL) semble un bon point de départ.
Nous redonnons le code ici car il a été très légèrement modifié :
-- ********** https://github.com/mongoq/tm1637-fpga ***********
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity tm1637 is
Generic (divider : integer := 1250); -- the divider must be set so that the result is a frequency of 20 kHz
Port ( clk25 : in std_logic;
data : std_logic_vector(15 downto 0);
scl : out std_logic;
sda : out std_logic
);
end tm1637;
architecture Behavioral of tm1637 is
impure function int_to_seg7(stelle : in integer; nrbit : in integer; in_digit0 : in std_logic_vector(3 downto 0); in_digit1 : in std_logic_vector(3 downto 0); in_digit2 : in std_logic_vector(3 downto 0); in_digit3 : in std_logic_vector(3 downto 0)) return std_logic is
variable counter : integer := 0;
variable bcd : integer := 0;
variable seg7 : std_logic_vector(7 downto 0);
variable dig : std_logic_vector(3 downto 0);
-- Werte hier !!! umziehen nach display aufdröselei
variable digit0 : std_logic_vector(3 downto 0) := "0001";
variable digit1 : std_logic_vector(3 downto 0) := "0001";
variable digit2 : std_logic_vector(3 downto 0) := "0001";
variable digit3 : std_logic_vector(3 downto 0) := "0001";
begin
digit0 := in_digit0;
digit1 := in_digit1;
digit2 := in_digit2;
digit3 := in_digit3;
case stelle is
when 1 => dig := digit0;
when 2 => dig := digit1;
when 3 => dig := digit2;
when 4 => dig := digit3;
when others => null;
end case;
case dig is
when "0000"=> seg7 := "00111111"; -- 0
when "0001"=> seg7 := "00000110"; -- 1
when "0010"=> seg7 := "01011011"; -- 2
when "0011"=> seg7 := "01001111"; -- 3
when "0100"=> seg7 := "01100110"; -- 4
when "0101"=> seg7 := "01101101"; -- 5
when "0110"=> seg7 := "01111101"; -- 6
when "0111"=> seg7 := "00000111"; -- 7
when "1000"=> seg7 := "01111111"; -- 8
when "1001"=> seg7 := "01101111"; -- 9
when "1010"=> seg7 := "01110111"; -- A
when "1011"=> seg7 := "01111100"; -- b
when "1100"=> seg7 := "00111001"; -- C
when "1101"=> seg7 := "01011110"; -- d
when "1110"=> seg7 := "01111001"; -- E
when "1111"=> seg7 := "01110001"; -- F
when others=> seg7 := "00000000"; -- 8:
end case;
return seg7(nrbit);
end int_to_seg7;
------------------------------------------------------------------------------------------------------------------------------------
signal clkdiv : integer range 0 to divider-1 := 0;
signal ce: std_logic := '0';
signal sm_counter : integer := 0;
signal clk_250k : std_logic := '0';
signal rdy : std_logic := '0';
signal reg_digit0 : std_logic_vector(3 downto 0) := "0000";
signal reg_digit1 : std_logic_vector(3 downto 0) := "0000";
signal reg_digit2 : std_logic_vector(3 downto 0) := "0000";
signal reg_digit3 : std_logic_vector(3 downto 0) := "0000";
signal cnt_rdy : std_logic_vector(1 downto 0);
-- DAS HIER RAUS !!!
--signal display : integer;
begin
process (clk25) begin
if rising_edge(clk25) then
if (clkdiv < divider-1) then
clkdiv <= clkdiv + 1;
ce <= '0';
else
clkdiv <= 0;
ce <= '1';
end if;
end if;
end process;
-- process (clk25) begin
-- if rising_edge(clk25) then
-- if (ce='1') then
-- clk_250k <= not clk_250k;
-- end if;
-- end if;
-- end process;
process(clk25)
begin
if rising_edge(clk25) then
if (ce='1') then
case sm_counter is
when 0 => scl <= '1'; sda <= '1';
when 1 => scl <= '1'; sda <= '1'; -- start condition
when 2 => sda <= '0';
when 3 => scl <= '0'; -- command 1
when 4 => scl <= '1';
when 5 => scl <= '0'; sda <= '0';
when 6 => scl <= '1';
when 7 => scl <= '0';
when 8 => scl <= '1';
when 9 => scl <= '0';
when 10 => scl <= '1';
when 11 => scl <= '0';
when 12 => scl <= '1';
when 13 => scl <= '0';
when 14 => scl <= '1';
when 15 => scl <= '0'; sda <= '1';
when 16 => scl <= '1';
when 17 => scl <= '0'; sda <= '0';
when 18 => scl <= '1';
when 19 => scl <= '0'; sda <= 'Z';
when 20 => scl <= '1';
when 21 => scl <= '0'; sda <= '0'; -- stop condition
when 22 => scl <= '1';
when 23 => sda <= '1'; -- start condition
when 24 => scl <= '1'; sda <= '0';
when 25 => scl <= '0'; sda <= '0'; -- command 2
when 26 => scl <= '1';
when 27 => scl <= '0';
when 28 => scl <= '1';
when 29 => scl <= '0'; sda <= '0';
when 30 => scl <= '1'; sda <= '0';
when 31 => scl <= '0'; sda <= '0';
when 32 => scl <= '1'; sda <= '0';
when 33 => scl <= '0'; sda <= '0';
when 34 => scl <= '1'; sda <= '0';
when 35 => scl <= '0'; sda <= '0';
when 36 => scl <= '1'; sda <= '0';
when 37 => scl <= '0'; sda <= '1';
when 38 => scl <= '1'; sda <= '1';
when 39 => scl <= '0'; sda <= '1';
when 40 => scl <= '1'; sda <= '1';
when 41 => scl <= '0'; sda <= 'Z';
when 42 => scl <= '1';
-- Daten 1 bis 58
when 43 => scl <= '0'; sda <= int_to_seg7(1, 0, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 44 => scl <= '1';
when 45 => scl <= '0'; sda <= int_to_seg7(1, 1, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 46 => scl <= '1';
when 47 => scl <= '0'; sda <= int_to_seg7(1, 2, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 48 => scl <= '1';
when 49 => scl <= '0'; sda <= int_to_seg7(1, 3, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 50 => scl <= '1';
when 51 => scl <= '0'; sda <= int_to_seg7(1, 4, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 52 => scl <= '1';
when 53 => scl <= '0'; sda <= int_to_seg7(1, 5, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 54 => scl <= '1';
when 55 => scl <= '0'; sda <= int_to_seg7(1, 6, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 56 => scl <= '1';
when 57 => scl <= '0'; sda <= int_to_seg7(1, 7, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 58 => scl <= '1';
-- Daten 1 bis hier
when 59 => scl <= '0'; sda <= 'Z';
when 60 => scl <= '1';
-- Daten 2 61 bis 76
when 61 => scl <= '0'; sda <= int_to_seg7(2, 0, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 62 => scl <= '1';
when 63 => scl <= '0'; sda <= int_to_seg7(2, 1, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 64 => scl <= '1';
when 65 => scl <= '0'; sda <= int_to_seg7(2, 2, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 66 => scl <= '1';
when 67 => scl <= '0'; sda <= int_to_seg7(2, 3, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 68 => scl <= '1';
when 69 => scl <= '0'; sda <= int_to_seg7(2, 4, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 70 => scl <= '1';
when 71 => scl <= '0'; sda <= int_to_seg7(2, 5, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 72 => scl <= '1';
when 73 => scl <= '0'; sda <= int_to_seg7(2, 6, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 74 => scl <= '1';
when 75 => scl <= '0'; sda <= int_to_seg7(2, 7, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 76 => scl <= '1';
-- Daten 2 bis hier
when 77 => scl <= '0'; sda <= 'Z';
when 78 => scl <= '1';
-- Daten 3 79 bis 94
when 79 => scl <= '0'; sda <= int_to_seg7(3, 0, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 80 => scl <= '1';
when 81 => scl <= '0'; sda <= int_to_seg7(3, 1, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 82 => scl <= '1';
when 83 => scl <= '0'; sda <= int_to_seg7(3, 2, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 84 => scl <= '1';
when 85 => scl <= '0'; sda <= int_to_seg7(3, 3, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 86 => scl <= '1';
when 87 => scl <= '0'; sda <= int_to_seg7(3, 4, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 88 => scl <= '1';
when 89 => scl <= '0'; sda <= int_to_seg7(3, 5, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 90 => scl <= '1';
when 91 => scl <= '0'; sda <= int_to_seg7(3, 6, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 92 => scl <= '1';
when 93 => scl <= '0'; sda <= int_to_seg7(3, 7, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 94 => scl <= '1';
-- Daten 3 bis hier
when 95 => scl <= '0'; sda <= 'Z';
when 96 => scl <= '1';
-- Daten 4 97 bis 112
when 97 => scl <= '0'; sda <= int_to_seg7(4, 0, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 98 => scl <= '1';
when 99 => scl <= '0'; sda <= int_to_seg7(4, 1, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 100 => scl <= '1';
when 101 => scl <= '0'; sda <= int_to_seg7(4, 2, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 102 => scl <= '1';
when 103 => scl <= '0'; sda <= int_to_seg7(4, 3, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 104 => scl <= '1';
when 105 => scl <= '0'; sda <= int_to_seg7(4, 4, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 106 => scl <= '1';
when 107 => scl <= '0'; sda <= int_to_seg7(4, 5, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 108 => scl <= '1';
when 109 => scl <= '0'; sda <= int_to_seg7(4, 6, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 110 => scl <= '1';
when 111 => scl <= '0'; sda <= int_to_seg7(4, 7, reg_digit0, reg_digit1, reg_digit2, reg_digit3);
when 112 => scl <= '1';
-- Daten 4 bis hier
when 113 => scl <= '0'; sda <= 'Z';
when 114 => scl <= '1';
when 115 => scl <= '0'; sda <= '0'; --stop
when 116 => scl <= '1';
when 117 => scl <= '1'; sda <= '1';
when 118 => scl <= '1'; sda <= '0'; -- start
when 119 => scl <= '0'; sda <= '0';
when 120 => scl <= '0'; sda <= '1';
--when 119 => scl <= '0'; sda <= '1';
when 121 => scl <= '1';
when 122 => scl <= '0';
when 123 => scl <= '1';
when 124 => scl <= '0';
when 125 => scl <= '1';
when 126 => scl <= '0';
when 127 => scl <= '1';
when 128 => scl <= '0'; sda <= '0';
when 129 => scl <= '1';
when 130 => scl <= '0';
when 131 => scl <= '1';
when 132 => scl <= '0';
when 133 => scl <= '1';
when 134 => scl <= '0'; sda <= '1';
when 135 => scl <= '1';
when 136 => scl <= '0'; sda <= 'Z';
when 137 => scl <= '1'; sda <= 'Z';
when 138 => scl <= '0'; sda <= '0';--stop
when 139 => scl <= '1'; sda <= '0';
when 140 => scl <= '1'; sda <= '1';
when 141 => scl <= '1'; sda <= '1';
when 142 => scl <= '1'; sda <= '1';
when others => scl <= '1'; sda <= '1';
end case;
if sm_counter = 200 then --250000 2sec
sm_counter <= 0;
else
sm_counter <= sm_counter + 1;
end if;
if sm_counter = 199 then
rdy <= '1';
else
rdy <= '0';
end if;
end if;
end if;
end process;
process(clk25)
begin
if rising_edge(clk25) then
if rdy = '1' then
if (ce ='1') then
reg_digit0 <= data(3 downto 0);
reg_digit1 <= data(7 downto 4);
reg_digit2 <= data(11 downto 8);
reg_digit3 <= data(15 downto 12);
end if;
end if;
end if;
end process;
end Behavioral;
Nous allons tout simplement maintenant fournir les données 16 bits nécessaire au bon fonctionnement de l'afficheur à l'aide du processeur. Bien sûr il nous faudra deux registres pour cela. Nous avons choisi ADCH et ADCL pour cela. Leur câblage peut sembles un peu étrange mais est lié à l'ordre d'affichage des 4 digits qui est fixé pas le circuit TM1637.
-------------------------------------------------------------------------------
--
-- Copyright (C) 2009, 2010 Dr. Juergen Sauermann
--
-- This code is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This code is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this code (see the file named COPYING).
-- If not, see http://www.gnu.org/licenses/.
--
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
--
-- Module Name: io - Behavioral
-- Create Date: 13:59:36 11/07/2009
-- Description: the I/O of a CPU (uart and general purpose I/O lines).
--
-------------------------------------------------------------------------------
--
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity io is
port ( I_CLK : in std_logic;
I_CLR : in std_logic;
I_ADR_IO : in std_logic_vector( 7 downto 0);
I_DIN : in std_logic_vector( 7 downto 0);
I_PINB : in std_logic_vector( 7 downto 0);
I_RD_IO : in std_logic;
I_RX : in std_logic;
I_WE_IO : in std_logic;
Q_PORTB : out std_logic_vector( 7 downto 0);
Q_DOUT : out std_logic_vector( 7 downto 0);
Q_INTVEC : out std_logic_vector( 5 downto 0);
--> changed 1 downto 0 to 7 downto 0 : 2011/10/18
Q_PORTC : out std_logic_vector( 7 downto 0);
-- added
Q_PORTD : out std_logic_vector( 7 downto 0);
--<
Q_TX : out std_logic;
tm1637scl, tm1637sda : out std_logic;
-- i2c signals
SCL : inout std_logic;
SDA : inout std_logic);
end io;
architecture Behavioral of io is
-- constantes pour ATMega16
constant TWBR : std_logic_vector(7 downto 0) := X"20";
constant TWSR : std_logic_vector(7 downto 0) := X"21";
constant TWAR : std_logic_vector(7 downto 0) := X"22";
constant TWDR : std_logic_vector(7 downto 0) := X"23";
constant ADCL : std_logic_vector(7 downto 0) := X"24";
constant ADCH : std_logic_vector(7 downto 0) := X"25";
constant UCSRB : std_logic_vector(7 downto 0) := X"2A";
constant UCSRA : std_logic_vector(7 downto 0) := X"2B";
constant UDR : std_logic_vector(7 downto 0) := X"2C";
constant PIND : std_logic_vector(7 downto 0) := X"30";
constant DDRD : std_logic_vector(7 downto 0) := X"31";
constant PORTD : std_logic_vector(7 downto 0) := X"32";
constant PINC : std_logic_vector(7 downto 0) := X"33";
constant DDRC : std_logic_vector(7 downto 0) := X"34";
constant PORTC : std_logic_vector(7 downto 0) := X"35";
constant PINB : std_logic_vector(7 downto 0) := X"36";
constant DDRB : std_logic_vector(7 downto 0) := X"37";
constant PORTB : std_logic_vector(7 downto 0) := X"38";
constant PINA : std_logic_vector(7 downto 0) := X"39";
constant DDRA : std_logic_vector(7 downto 0) := X"3A";
constant PORTA : std_logic_vector(7 downto 0) := X"3B";
constant EEDR : std_logic_vector(7 downto 0) := X"3D";
constant EEARL : std_logic_vector(7 downto 0) := X"3E";
constant EEARH : std_logic_vector(7 downto 0) := X"3F";
constant UCSRC : std_logic_vector(7 downto 0) := X"40";
constant TCNT0 : std_logic_vector(7 downto 0) := X"52";
constant TCCR0 : std_logic_vector(7 downto 0) := X"53";
constant TWCR : std_logic_vector(7 downto 0) := X"56";
constant TIMSK : std_logic_vector(7 downto 0) := X"59";
constant OCR0 : std_logic_vector(7 downto 0) := X"5C";
component uart
generic(CLOCK_FREQ : std_logic_vector(31 downto 0);
BAUD_RATE : std_logic_vector(27 downto 0));
port( I_CLK : in std_logic;
I_CLR : in std_logic;
I_RD : in std_logic;
I_WE : in std_logic;
I_RX : in std_logic;
I_TX_DATA : in std_logic_vector(7 downto 0);
Q_RX_DATA : out std_logic_vector(7 downto 0);
Q_RX_READY : out std_logic;
Q_TX : out std_logic;
Q_TX_BUSY : out std_logic);
end component;
component topi2c is port(
clk,Reset : in std_logic;
TWWR,TWSTA,TWSTO,TWRD,TWEA,TWINT,TWEN : in std_logic;
TWBR : in std_logic_vector(7 downto 0); -- Bit Rate Register
IN_TWDR : in std_logic_vector(7 downto 0); -- Data Register
OUT_TWDR : out std_logic_vector(7 downto 0); -- Data Register
O_TWINT : out std_logic; -- pour gestion particuliere de TWINT
-- i2c signals
SCL : inout std_logic;
SDA : inout std_logic
);
end component topi2c;
component tm1637 is
Generic (divider : integer := 1250); -- the divider must be set so that the result is a frequency of 20 kHz
Port ( clk25 : in std_logic;
data : std_logic_vector(15 downto 0);
scl : out std_logic;
sda : out std_logic
);
end component tm1637;
signal U_RX_READY : std_logic;
signal U_TX_BUSY : std_logic;
signal U_RX_DATA : std_logic_vector( 7 downto 0);
signal L_INTVEC : std_logic_vector( 5 downto 0);
signal L_LEDS : std_logic;
signal L_RD_UART : std_logic;
signal L_RX_INT_ENABLED : std_logic;
signal L_TX_INT_ENABLED : std_logic;
signal L_WE_UART : std_logic;
--> added 2011/10/19
signal baud_clk : std_logic;
--<
signal s_TWCR, s_TWDR, s_TWBR, s_TWSR, s_TWDR_O : std_logic_vector(7 downto 0);
signal s_TWINT_O : std_logic;
signal s_TWINT_tick : std_logic;
--TM1637
signal s_data : std_logic_vector(15 downto 0);
begin
urt: uart
generic map(CLOCK_FREQ => std_logic_vector(conv_unsigned(50000000, 32)),
BAUD_RATE => std_logic_vector(conv_unsigned( 38400, 28)))
port map( I_CLK => I_CLK, --baud_clk,--
I_CLR => I_CLR,
I_RD => L_RD_UART,
I_WE => L_WE_UART,
I_TX_DATA => I_DIN(7 downto 0),
I_RX => I_RX,
Q_TX => Q_TX,
Q_RX_DATA => U_RX_DATA,
Q_RX_READY => U_RX_READY,
Q_TX_BUSY => U_TX_BUSY);
--> added 2011/10/19
baud_process: process(I_CLK) begin
if rising_edge(I_CLK) then
baud_clk <= not baud_clk;
end if;
end process;
--<
i2c: topi2c port map (
clk => I_clk,
Reset => I_CLR,
--TWCR[0]=TWIE non géré et TWCR[7]=TWINT gestion particulière
TWWR => s_TWCR(1),
TWSTA => s_TWCR(5),
TWSTO => s_TWCR(4),
TWRD => s_TWCR(3),
TWEA => s_TWCR(6),
TWINT => s_TWINT_tick,
TWEN => s_TWCR(2),
TWBR => s_TWBR, -- Bit Rate Register
IN_TWDR => s_TWDR, -- Data Register
OUT_TWDR => s_TWDR_O, -- Data Register
O_TWINT => s_TWINT_O, -- pour gestion particuliere de TWINT
-- i2c signals
SCL => SCL,
SDA => SDA
);
s_TWINT_tick <= I_WE_IO when ((I_ADR_IO = TWCR) and (I_DIN(7)='1')) else '0';
-- IO read process
--
tm1637Manager:tm1637 port map (
clk25 => I_clk,
data => s_data,
scl => tm1637scl,
sda => tm1637sda
);
iord: process(I_ADR_IO, I_PINB,
U_RX_DATA, U_RX_READY, L_RX_INT_ENABLED,
U_TX_BUSY, L_TX_INT_ENABLED,s_twcr,s_twint_o,s_twdr_o,s_twbr)
begin
-- addresses for mega8 device (use iom8.h or #define __AVR_ATmega8__).
--
case I_ADR_IO is
-- gestion i2c
when TWCR => Q_DOUT(6 downto 0) <= s_TWCR(6 downto 0);
-- TWINT a une gestion un peu particuliere
Q_DOUT(7) <= s_TWINT_O;
when TWSR => Q_DOUT <= s_TWSR;
when TWDR => Q_DOUT <= s_TWDR_O;
when TWBR => Q_DOUT <= s_TWBR;
when UCSRB => Q_DOUT <= -- UCSRB:
L_RX_INT_ENABLED -- Rx complete int enabled.
& L_TX_INT_ENABLED -- Tx complete int enabled.
& L_TX_INT_ENABLED -- Tx empty int enabled.
& '1' -- Rx enabled
& '1' -- Tx enabled
& '0' -- 8 bits/char
& '0' -- Rx bit 8
& '0'; -- Tx bit 8
when UCSRA => Q_DOUT <= -- UCSRA:
U_RX_READY -- Rx complete
& not U_TX_BUSY -- Tx complete
& not U_TX_BUSY -- Tx ready
& '0' -- frame error
& '0' -- data overrun
& '0' -- parity error
& '0' -- double dpeed
& '0'; -- multiproc mode
when UDR => Q_DOUT <= U_RX_DATA; -- UDR
when UCSRC => Q_DOUT <= -- UCSRC
'1' -- URSEL
& '0' -- asynchronous
& "00" -- no parity
& '1' -- two stop bits
& "11" -- 8 bits/char
& '0'; -- rising clock edge
when PINB => Q_DOUT <= I_PINB; -- PINB
when others => Q_DOUT <= X"AA";
end case;
end process;
-- IO write process
--
iowr: process(I_CLK)
begin
if (rising_edge(I_CLK)) then
if (I_CLR = '1') then
L_RX_INT_ENABLED <= '0';
L_TX_INT_ENABLED <= '0';
elsif (I_WE_IO = '1') then
case I_ADR_IO is
when PORTB => -- PORTB
Q_PORTB <= I_DIN;
--L_LEDS <= not L_LEDS;
when PORTC => -- PORTC
Q_PORTC <= I_DIN;
when PORTD => -- PORTD
Q_PORTD <= I_DIN;
when UCSRB => -- UCSRB
L_RX_INT_ENABLED <= I_DIN(7);
L_TX_INT_ENABLED <= I_DIN(6);
when ADCL =>
s_data(15 downto 12) <= I_DIN(3 downto 0);
s_data(11 downto 8) <= I_DIN(7 downto 4);
when ADCH =>
s_data(7 downto 4) <= I_DIN(3 downto 0);
s_data(3 downto 0) <= I_DIN(7 downto 4);
-- gestion i2c
when TWCR => s_TWCR <= I_DIN;
when TWDR => s_TWDR <= I_DIN;
when TWBR => s_TWBR <= I_DIN;
when UCSRA => -- UCSRA: handled by uart
when UDR => -- UDR: handled by uart
when X"40" => -- UCSRC/UBRRH: (ignored)
when others =>
end case;
end if;
end if;
end process;
-- interrupt process
--
ioint: process(I_CLK)
begin
if (rising_edge(I_CLK)) then
if (I_CLR = '1') then
L_INTVEC <= "000000";
else
case L_INTVEC is
-- vector 12 ??
when "101011" => -- vector 11 interrupt pending.
if (L_RX_INT_ENABLED and U_RX_READY) = '0' then
L_INTVEC <= "000000";
end if;
-- vector 14 ??
when "101100" => -- vector 12 interrupt pending.
if (L_TX_INT_ENABLED and not U_TX_BUSY) = '0' then
L_INTVEC <= "000000";
end if;
when others =>
-- no interrupt is pending.
-- We accept a new interrupt.
--
if (L_RX_INT_ENABLED and U_RX_READY) = '1' then
L_INTVEC <= "101011"; -- _VECTOR(11)
elsif (L_TX_INT_ENABLED and not U_TX_BUSY) = '1' then
L_INTVEC <= "101100"; -- _VECTOR(12)
else
L_INTVEC <= "000000"; -- no interrupt
end if;
end case;
end if;
end if;
end process;
L_WE_UART <= I_WE_IO when (I_ADR_IO = X"2C") else '0'; -- write UART UDR
L_RD_UART <= I_RD_IO when (I_ADR_IO = X"2C") else '0'; -- read UART UDR
Q_INTVEC <= L_INTVEC;
end Behavioral;
Il ne vous reste qu'à sortir les SDA et SCL de ce module jusqu'au processeur et à réaliser un programme de fonctionnement. En voici un exemple :
#include <avr/io.h>
//#include <avr/interrupt.h>
#undef F_CPU
#define F_CPU 25000000UL
#include "util/delay.h"
int main() {
uint16_t cmpt=0;
// init
_delay_ms(10);
// loop
while(1) {
PORTC=cmpt;
_delay_ms(100);
cmpt++;
// on donne à manger au TM1737 :
ADCL = cmpt;
ADCH = (cmpt>>8);
}
return 0;
}
Voir aussi
[modifier | modifier le wikicode]- Voir comment utiliser le "Pin Planer" ou l' "Assignement Editor" pour réaliser des entrées sorties pour l'i2c.
- github tm1637 pour FPGA (VHDL)
Étude du Shield LCD keypad
[modifier | modifier le wikicode]Ce shield est décrit ICI. Il peur être trouvé pour environ 3,70 € sur Internet (au moment de l'écriture de ces lignes).
- Sa première particularité est qu'il doit être alimenté en 5V
- Sa deuxième particularité est que les 5 boutons poussoirs sont reliés à une seule entrée qui devient donc analogique. La lecture de ces boutons nécessite donc un convertisseur analogique numérique. ATTENTION la tension analogique en question dépasse 3,3 V !!!
La question est donc de vérifier que malgré une alimentation à 5V, l'écran LCD fonctionne correctement. Pour ce faire, nous avons câblé le shield avec une carte MSP432 qui permet une alimentation du shield en 5V mais qui possède un processeur de type ARM qui fonctionne en 3,3 V. L'essai ayant été concluant, nous sommes prêt à tenter notre chance avec un FPGA. Nous allons utiliser la carte DE2-115 qui possède un connecteur d'extension sur lequel le 5V est disponible, ce qui n'est pas le cas pour les cartes de Digilent (les connecteurs PMod n'utilisent que du 3,3 V).
Le code utilisé a nécessité l'installation de la librairie LiquidCrystal avec Energia :
#include <LiquidCrystal.h>
LiquidCrystal lcd(6, 7, 2, 3, 4, 5); // select the pins used on the LCD panel
// dans l'ordre P1.5 , P4.3, P4.1, P3.3, P3.2 P6.0
// reliés à : 9, 8, 7, -6 , -5, 4 (repérés sur Arduino)
unsigned long tepTimer ;
void setup(){
lcd.begin(16, 2); // start the library
}
void loop(){
lcd.setCursor(0, 0); // set the LCD cursor position
int val; // variable to store the value coming from the analog pin
double data; // variable to store the temperature value coming from the conversion formula
val=analogRead(A1); // read the analog in value:
data = (double) val * (5/10.24); // temperature conversion formula
if(millis() - tepTimer > 500){ // output a temperature value per 500ms
tepTimer = millis();
// print the results to the lcd
lcd.print("T: ");
lcd.print(data);
lcd.print("C");
}
}
Les paramètres de lcd sont les noms Energia de la carte MSP432.
Module VHDL seul
[modifier | modifier le wikicode]Nous allons essayer, dans cette section, d'utiliser un module VHDL touvé chez Opencores. Il se trouve dans Projets -> Other -> 16x2 LCD controller et a été réalisé par Daniel Drescher.
Nous rappelons que nous avons décidé d'utiliser la carte DE2-115 de Terasic équipée d'un Cyclone IV de chez Intel/Altera. Ceux qui connaissent la carte savent qu'elle est déjà équipée d'un affichage LCD. Mais nous allons en rajouter quand même un deuxième pour comprendre les problèmes à résoudre dans ce cas. En fait il n'y a aucun problème, le cœur trouvé sur Opencores.org fonctionne immédiatement. Reste quand même à comprendre un peu son fonctionnement pour aller plus loin.
Voici en résumé les connexions que l'on a réalisé :
LCD | en | rs | d(7) | d(6) | d(5) | d(4) |
---|---|---|---|---|---|---|
Arduino Pin | 9 | 8 | 7 | 6 | 5 | 4 |
DE2-115 | GPIO[13] | GPIO[11] | GPIO[7] | GPIO[5] | GPIO[3] | GPIO[1] |
Les numéros des broches avec la convention Arduino sont données ici pour se repérer pour les broches de Shield. Pour celui qui n'a jamais utilisé d'Arduino, espérons que le schéma suffira.
Le ficher de contraintes correspondant à l'image ci-contre est donné au format CSV :
To,Direction,Location,I/O Bank,VREF Group,I/O Standard,Reserved CLK,Input,PIN_Y2,2,B2_N0,3.3-V LVTTL, lcd_e,Output,PIN_AF15,4,B4_N2,3.3-V LVTTL, lcd_rs,Output,PIN_AF16,4,B4_N2,3.3-V LVTTL, lcd_db[7],Output,PIN_AE16,4,B4_N2,3.3-V LVTTL, lcd_db[6],Output,PIN_Y16,4,B4_N0,3.3-V LVTTL, lcd_db[5],Output,PIN_Y17,4,B4_N0,3.3-V LVTTL, lcd_db[4],Output,PIN_AC15,4,B4_N2,3.3-V LVTTL,
Adapter ceci pour une autre carte est relativement simple. Il suffit de changer le fichier de contraintes.
Reste cependant à répondre à une question : peut-on directement mettre ce shield sur la carte DE0-nano SOC qui possède un connecteur pour Shield de type Arduino ? La réponse est : affaire à suivre. Le problème est lié à la façon dont sont câblés les boutons poussoirs : quand aucun bouton poussoir n'est appuyé, vous vous trouvez avec 5V sur la broche A0 de l'Arduino, qui dans le cas de la carte en question est relié à un convertisseur analogique numérique extérieur au FPGA, mais qui semble ne supporter que 4,096 V (tension de référence par défaut). Le circuit utilisé pour la conversion analogique numérique est un LTC2308. Le parcourt rapide de sa documentation (du LTC2308) m'a permis de déterminer que son alimentation est en 5V, mais pas si ces 5V peuvent être utilisés en tension de référence. Donc personnellement je n'utiliserai pas ce shield directement sur la DE0-nano SOC avant d'avoir lu une documentation plus complète. Mais vous pouvez utiliser ce shield avec de simples fils, un peu comme sur la figure, en laissant tomber les boutons pour le moment.
Exercice 1
[modifier | modifier le wikicode]On vous demande de changer l'affichage réalisé par le module de chez Opencores.org. Vous pourrez y afficher ce que vous désirez, le but étant de vous montrer comment les choses se passent.
Seul le fichier de contraintes est donné, le reste, les sources VHDL, peut être récupéré chez Opencores.org :
To,Direction,Location,I/O Bank,VREF Group,I/O Standard,Reserved CLK,Input,PIN_Y2,2,B2_N0,3.3-V LVTTL, lcd_e,Output,PIN_AF15,4,B4_N2,3.3-V LVTTL, GPIO[12],Bidir,PIN_AD19,4,B4_N0,3.3-V LVTTL, lcd_rs,Output,PIN_AF16,4,B4_N2,3.3-V LVTTL, GPIO[10],Bidir,PIN_AC19,4,B4_N0,3.3-V LVTTL, GPIO[9],Bidir,PIN_AE15,4,B4_N2,3.3-V LVTTL, GPIO[8],Bidir,PIN_AD15,4,B4_N2,3.3-V LVTTL, lcd_db[7],Output,PIN_AE16,4,B4_N2,3.3-V LVTTL, GPIO[6],Bidir,PIN_AD21,4,B4_N0,3.3-V LVTTL, lcd_db[6],Output,PIN_Y16,4,B4_N0,3.3-V LVTTL, GPIO[4],Bidir,PIN_AC21,4,B4_N0,3.3-V LVTTL, lcd_db[5],Output,PIN_Y17,4,B4_N0,3.3-V LVTTL, GPIO[2],Bidir,PIN_AB21,4,B4_N0,3.3-V LVTTL, lcd_db[4],Output,PIN_AC15,4,B4_N2,3.3-V LVTTL, GPIO[0],Bidir,PIN_AB22,4,B4_N0,3.3-V LVTTL, led_out,Output,PIN_E21,7,B7_N0,2.5 V,
Câbler le shield directement sur un PORT
[modifier | modifier le wikicode]En général l'utilisation d'un afficheur LCD avec un microcontrôleur se fait naturellement de cette manière. Dans un FPGA, il est possible de faire autrement mais il est bon de commencer par le plus utilisé. Le processeur enfoui que l'on utilisera sera un ATTiny861.
Si vous avez réalisé l'exercice 1, vous avez compris que la gestion des deux lignes de 16 caractères nécessite 32 octets, soit 32 PORTs... et ceci est un problème car c'est ce que possède le Tiny... et si on les utilise tous ici, on ne pourra plus faire grand chose d'autre ! Il nous faudra trouver donc une autre solution.
Dans ce qui suit nous allons montrer encore une fois comment on peut libérer des ressources de code en réalisant un périphérique, d'abord très simplifié, puis général. Mais pour commencer, nous allons montrer d'abord qu'un simple affichage peut être réalisé en interfaçant directement l'afficheur LCD sans passer par la partie matérielle de l'exercice 1.
Exercice 2
[modifier | modifier le wikicode]Réaliser une interface directe de l'afficheur à un Tiny861 et montrer qu'un texte général peut être affiché.
Indication : on adaptera le code C ci-dessous au matériel.
// lcd.c
//
// Copyright 2014 Michel Doussot <michel@mustafar>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301, USA.
#include <avr/io.h>
#define RS 0x04
#define E 0x02
#define RW 0x08
#define DATAS 0xF0
// unite approximative 2us
void delai(unsigned long int delai) {
volatile long int i=0;
for(i=0;i<delai;i+=1);
}
// portB :
// b7 b6 b5 b4 b3 b2 b1 b0
// D3 D2 D1 D0 RW RS E CE0
void rs_haut(void) {
PORTB = PORTB | RS;
}
void rs_bas(void) {
PORTB = PORTB & ~RS;
}
void rw_haut(void) {
PORTB = PORTB | RW;
}
void rw_bas(void) {
PORTB = PORTB & ~RW;
}
void e_haut(void) {
PORTB = PORTB | E;
delai(8);
}
void e_bas(void) {
PORTB = PORTB & ~E;
delai(8);
}
void e_puls(void) {
e_haut();
e_bas();
}
void ecris_4(unsigned char valeur) {
unsigned char v;
v = (valeur << 4) & DATAS;
PORTB = PORTB & ~DATAS ;
PORTB = PORTB | v ;
e_puls();
}
void ecris_8(unsigned char valeur) {
unsigned char v;
v = valeur & DATAS;
PORTB = PORTB & ~DATAS ;
PORTB = PORTB | v ;
e_puls();
v = (valeur << 4) & DATAS;
PORTB = PORTB & ~DATAS ;
PORTB = PORTB | v ;
e_puls();
}
void setup() {
PORTB = 0;
delai(6000);
ecris_4(0x03);
delai(1600);
ecris_4(0x03);
delai(800);
ecris_4(0x03);
delai(800);
ecris_4(0x02);
delai(40);
ecris_4(0x02);
ecris_4(0x08);
delai(40);
ecris_4(0x00);
ecris_4(0x06);
delai(40);
ecris_4(0x00);
ecris_4(0x0C);
delai(40);
ecris_4(0x00);
ecris_4(0x01);
delai(800);
}
void writecar(char car) {
rs_haut();
ecris_8((unsigned char)car);
}
void writestr(char *chaine) {
rs_haut();
while (*chaine) {
ecris_8((unsigned char)*chaine++);
}
}
Le point important de ce code par rapport à tout ce qui a été vu jusqu'ici est l'apparition de lignes comme PORTB = PORTB | ... qui montrent que PORTB doit être en entrée et en sortie.
Voici en résumé ce que l'on a fait d'un point de vue matériel. Vous devez repérer les lignes qui gèrent à la fois le PORTB en sortie et en entrée. Attention on utilise les deux process écriture et lecture pour cela : seule la ligne concernant le PORTB est importante.
entity microcontroleur is
Port ( clk : in STD_LOGIC;
Rst : in STD_LOGIC;
sw : in STD_LOGIC_VECTOR (7 downto 0);
lcd_e : out std_logic;
lcd_rs : out std_logic;
lcd_rw : out std_logic;
lcd_db : out std_logic_vector(7 downto 4);
Q_PORTB : out STD_LOGIC_VECTOR (7 downto 0)
);
end microcontroleur;
architecture microcontroleur_architecture of microcontroleur is
--Registres et PORTs de l'ATTiny861
constant OCR1A : std_logic_vector(5 downto 0) := "101101";
constant OCR1B : std_logic_vector(5 downto 0) := "101100";
constant PORTA : std_logic_vector(5 downto 0) := "011011";
constant DDRA : std_logic_vector(5 downto 0) := "011010";
constant PINA : std_logic_vector(5 downto 0) := "011001";
constant PORTB : std_logic_vector(5 downto 0) := "011000";
--..... Beaucoup de lignes sont retirées ici mais peuvent être retrouvées ailleurs
iowr: process(CLK)
begin
if (rising_edge(CLK)) then
if (IO_wr = '1') then
case IO_A is
-- addresses for tiny861 device (use io.h).
--
when PORTA => -- PORTA=X"1B" (0X3B)
s_2digits <= IO_Dwr;
when PORTB => -- PORTB=X"18" (0X38)
s_PORTB <= IO_Dwr;
when others =>
end case;
end if;
end if;
end process;
-- ne pas oublier de sortir le signal :
Q_PORTB <= s_PORTB;
-- IO read process
--
iord: process(IO_rd,IO_A)
begin
-- addresses for tinyX6 device (use iom8.h).
--
if IO_rd = '1' then
case IO_A is
when PINA => IO_Drd <= sw; -- PINA=X"19" (0X39)
when PORTB => IO_Drd <= s_PORTB; -- PORTB=X"18" (0X38)
when others => IO_Drd <= X"AA";
end case;
end if;
end process;
end microcontroleur_architecture;
Le fichier de contraintes est pour la DE-115 avec les mêmes branchement que l'exercice 1 :
To,Direction,Location,I/O Bank,VREF Group,I/O Standard,Reserved CLK,Input,PIN_Y2,2,B2_N0,3.3-V LVTTL, SW[7],Input,PIN_AC25,5,B5_N2,2.5 V, SW[6],Input,PIN_AB26,5,B5_N1,2.5 V, SW[5],Input,PIN_AD26,5,B5_N2,2.5 V, SW[4],Input,PIN_AC26,5,B5_N2,2.5 V, SW[3],Input,PIN_AB27,5,B5_N1,2.5 V, SW[2],Input,PIN_AD27,5,B5_N2,2.5 V, SW[1],Input,PIN_AC27,5,B5_N2,2.5 V, SW[0],Input,PIN_AC28,5,B5_N2,2.5 V, Rst,Input,PIN_AB28,5,B5_N1,2.5 V, Q_PORTB[1],Output,PIN_AF15,4,B4_N2,3.3-V LVTTL, GPIO[12],Bidir,PIN_AD19,4,B4_N0,3.3-V LVTTL, Q_PORTB[2],Output,PIN_AF16,4,B4_N2,3.3-V LVTTL, GPIO[10],Bidir,PIN_AC19,4,B4_N0,3.3-V LVTTL, GPIO[9],Bidir,PIN_AE15,4,B4_N2,3.3-V LVTTL, GPIO[8],Bidir,PIN_AD15,4,B4_N2,3.3-V LVTTL, Q_PORTB[7],Output,PIN_AE16,4,B4_N2,3.3-V LVTTL, GPIO[6],Bidir,PIN_AD21,4,B4_N0,3.3-V LVTTL, Q_PORTB[6],Output,PIN_Y16,4,B4_N0,3.3-V LVTTL, GPIO[4],Bidir,PIN_AC21,4,B4_N0,3.3-V LVTTL, Q_PORTB[5],Output,PIN_Y17,4,B4_N0,3.3-V LVTTL, GPIO[2],Bidir,PIN_AB21,4,B4_N0,3.3-V LVTTL, Q_PORTB[4],Output,PIN_AC15,4,B4_N2,3.3-V LVTTL, GPIO[0],Bidir,PIN_AB22,4,B4_N0,3.3-V LVTTL,
Pour le code C nous avons ajouté le code suivant :
void command (uint8_t value) {
rs_bas();
ecris_8(value);
}
#define LCD_CLEARDISPLAY 0x01
void clearScreen() {
command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
delai(1000); // this command takes a long time!
}
#define LCD_SETDDRAMADDR 0x80
void setCursor(uint8_t col, uint8_t row)
{
col = col & 0x0F; // %16
row = row & 0x01; // %2
command(LCD_SETDDRAMADDR | (col + 0x40*row));
}
int main(void) {
unsigned char tmp;
//DDRA = 0xFF;
//DDRB = 0xFF;
setup();
tmp = 0;
writestr("Hello Microsoft ");
setCursor(2,1);
writestr("Hello Linux");
while (1) {
PORTA = tmp;
delai(100000);
tmp += 1;
}
return 0;
}
Ce qui est fait dans le while(1) n'a aucune sorte d'importance.
Il est possible de continuer à faire évoluer la librairie d'affichage à l'infini pour une utilisation plus simple. Mais nous allons maintenant vraiment nous intéresser à la réalisation d'un périphérique capable d'afficher. Pour commencer nous allons abandonner le caractère généraliste de l'afficheur pour nous cantonner à un affichage très spécialisé.
Exercice 3
[modifier | modifier le wikicode]Pouvez-vous imaginer et réaliser une interface (même partielle) entre le fichier « lcd16x2_ctrl.vhd » et notre Tiny861 pour au moins gérer un affichage des deux chiffres d'un compteur BCD. Évidemment on laissera tomber le fichier « lcd16x2_ctrl_demo.vhd » qui n'est là que pour montrer comment l'ensemble fonctionne.
Indications :
- le Tiny861 sur carte Altera possède déjà son chapitre de Travaux Pratiques.
- le compteur BCD est réalisé en C dans le processeur... Il est sur 8 bits et les 4 bits de poids faibles représentent les unités tandis que les 4 bits de poids forts représentent les dizaines. Ce que l'on veut donc c'est qu'une instruction de type PORTX=0x23 affiche directement 23 quelque part sur votre afficheur LCD.
Nous avons déjà utilisé les sous-programmes d'incrémentation et décrémentation ICI
#include "avr/io.h"
#undef F_CPU
#define F_CPU 15000000UL
#include "util/delay.h"
void incrementBCD(unsigned char *cnt);
void decrementBCD(unsigned char *cnt);
//***********************************************************************
// main
//***********************************************************************
int main (void) {
unsigned char cmpt=0,sw;
while(1) {
// compteur simple
PORTA = cmpt;
sw = PINA;
if (sw & 0x01)
incrementBCD(&cmpt);
else
decrementBCD(&cmpt);
_delay_ms(1000); // on verra passer les caractères
}
return 0;
}
void incrementBCD(unsigned char *cnt) {
(*cnt)++;
if ((*cnt & 0x0F) > 0x09) *cnt += 6;
if ((*cnt & 0xF0) > 0x90) *cnt = 0;
}
void decrementBCD(unsigned char *cnt) {
(*cnt)--;
if ((*cnt & 0x0F) == 0x0F) *cnt -= 6;
if ((*cnt & 0xF0) == 0xF0) *cnt = 0x99;
}
Comme vous pouvez le voir, cmpt est sorti sur un PORT et provoquera l'affichage de deux chiffres.
Le composant est interfacé de la manière suivante :
----------------------------------------------------------------------------------
-- Company:
-- Engineer:
--
-- Create Date: 08:57:48 08/26/2014
-- Design Name:
-- Module Name: microcontroleur - microcontroleur_architecture
-- Project Name:
-- Target Devices:
-- Tool versions:
-- Description:
--
-- Dependencies:
--
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity microcontroleur is
Port ( clk : in STD_LOGIC;
Rst : in STD_LOGIC;
sw : in STD_LOGIC_VECTOR (7 downto 0);
lcd_e : out std_logic;
lcd_rs : out std_logic;
lcd_rw : out std_logic;
lcd_db : out std_logic_vector(7 downto 4)
);
end microcontroleur;
architecture microcontroleur_architecture of microcontroleur is
--Registres et PORTs de l'ATTiny861
constant OCR1A : std_logic_vector(5 downto 0) := "101101";
constant OCR1B : std_logic_vector(5 downto 0) := "101100";
constant PORTA : std_logic_vector(5 downto 0) := "011011";
constant DDRA : std_logic_vector(5 downto 0) := "011010";
constant PINA : std_logic_vector(5 downto 0) := "011001";
constant PORTB : std_logic_vector(5 downto 0) := "011000";
constant DDRB : std_logic_vector(5 downto 0) := "010111";
constant PINB : std_logic_vector(5 downto 0) := "010110";
constant ADCH : std_logic_vector(5 downto 0) := "000101";
constant ADCL : std_logic_vector(5 downto 0) := "000100";
--Registres non présents dans l'ATTiny861
constant UDR : std_logic_vector(5 downto 0) := "000011";
constant UCSRA : std_logic_vector(5 downto 0) := "000010";
constant UCSRB : std_logic_vector(5 downto 0) := "000001";
component mcu_core is
Port (
Clk : in std_logic;
Rst : in std_logic; -- Reset core when Rst='1'
En : in std_logic; -- CPU stops when En='0', could be used to slow down cpu to save power
-- PM
PM_A : out std_logic_vector(15 downto 0);
PM_Drd : in std_logic_vector(15 downto 0);
-- DM
DM_A : out std_logic_vector(15 downto 0); -- 0x00 - xxxx
DM_Areal : out std_logic_vector(15 downto 0); -- 0x60 - xxxx (same as above + io-adr offset)
DM_Drd : in std_logic_vector(7 downto 0);
DM_Dwr : out std_logic_vector(7 downto 0);
DM_rd : out std_logic;
DM_wr : out std_logic;
-- IO
IO_A : out std_logic_vector(5 downto 0); -- 0x00 - 0x3F
IO_Drd : in std_logic_vector(7 downto 0);
IO_Dwr : out std_logic_vector(7 downto 0);
IO_rd : out std_logic;
IO_wr : out std_logic;
-- OTHER
OT_FeatErr : out std_logic; -- Feature error! (Unhandled part of instruction)
OT_InstrErr : out std_logic -- Instruction error! (Unknown instruction)
);
end component mcu_core;
--PM
component pm is
Port (
Clk : in std_logic;
rst : in std_logic; -- Reset when Rst='1'
-- PM
PM_A : in std_logic_vector(15 downto 0);
PM_Drd : out std_logic_vector(15 downto 0)
);
end component pm;
component dm is
Port ( clk : in STD_LOGIC;
addr : in STD_LOGIC_VECTOR (15 downto 0);
dataread : out STD_LOGIC_VECTOR (7 downto 0);
datawrite : in STD_LOGIC_VECTOR (7 downto 0);
rd : in STD_LOGIC;
wr : in STD_LOGIC);
end component dm;
component lcd16x2_ctrl is
generic (
CLK_PERIOD_NS : positive := 20); -- 50MHz
port (
clk : in std_logic;
rst : in std_logic;
lcd_e : out std_logic;
lcd_rs : out std_logic;
lcd_rw : out std_logic;
lcd_db : out std_logic_vector(7 downto 4);
line1_buffer : in std_logic_vector(127 downto 0); -- 16x8bit
line2_buffer : in std_logic_vector(127 downto 0));
end component lcd16x2_ctrl;
signal PM_A : std_logic_vector(15 downto 0);
signal PM_Drd : std_logic_vector(15 downto 0);
-- DM
signal DM_A : std_logic_vector(15 downto 0); -- 0x00 - xxxx
signal DM_Areal : std_logic_vector(15 downto 0); -- 0x60 - xxxx (same as above + io-adr offset)
signal DM_Drd : std_logic_vector(7 downto 0);
signal DM_Dwr : std_logic_vector(7 downto 0);
signal DM_rd : std_logic;
signal DM_wr : std_logic;
-- IO
signal IO_A : std_logic_vector(5 downto 0); -- 0x00 - 0x3F
signal IO_Drd : std_logic_vector(7 downto 0);
signal IO_Dwr : std_logic_vector(7 downto 0);
signal IO_rd : std_logic;
signal IO_wr : std_logic;
signal IO_DrdA : std_logic_vector(7 downto 0);
signal IO_DrdB : std_logic_vector(7 downto 0);
signal s_2digits : std_logic_vector(7 downto 0);
begin
core : mcu_core Port map (
Clk => clk,
Rst => Rst,
En => '1',
-- PM
PM_A => PM_A,
PM_Drd => PM_Drd,
-- DM
DM_A => DM_A,
DM_Areal => DM_Areal,
DM_Drd => DM_Drd,
DM_Dwr => DM_Dwr,
DM_rd => DM_rd,
DM_wr => DM_wr,
-- IO
IO_A => IO_A,
IO_Drd => IO_Drd,
IO_Dwr => IO_Dwr,
IO_rd => IO_rd,
IO_wr => IO_wr,
-- OTHER
OT_FeatErr => open,
OT_InstrErr => open
);
prgmem : pm port map (
Clk => clk,
Rst => '0',
-- PM
PM_A => PM_A,
PM_Drd => PM_Drd
);
datamem : dm port map (
clk => clk,
addr => DM_A,
dataread => DM_Drd,
datawrite => DM_Dwr,
rd => DM_rd,
wr => DM_wr
);
LCD : lcd16x2_ctrl port map (
clk => clk,
rst => Rst,
lcd_e => lcd_e,
lcd_rs => lcd_rs,
lcd_rw => lcd_rw,
lcd_db => lcd_db,
line1_buffer => X"202048656c6c6f20576f726c64212020", --" Hello World! "
line2_buffer(127 downto 96) => X"20202020",
line2_buffer(95 downto 92) => X"3",
line2_buffer(91 downto 88)=> s_2digits(7 downto 4),
line2_buffer(87 downto 84) => X"3",
line2_buffer(83 downto 80)=> s_2digits(3 downto 0),
line2_buffer(79 downto 0) => X"20202020202020202020"
);
-- IO write process
iowr: process(CLK)
begin
if (rising_edge(CLK)) then
if (IO_wr = '1') then
case IO_A is
-- addresses for tiny861 device (use io.h).
when PORTA => -- PORTA=X"1B" (0X3B)
s_2digits <= IO_Dwr;
-- when PORTB => -- PORTB=X"18" (0X38)
when others =>
end case;
end if;
end if;
end process;
-- IO read process
--
iord: process(IO_rd,IO_A)
begin
-- addresses for tinyX6 device (use iom8.h).
--
if IO_rd = '1' then
case IO_A is
when PINA => IO_Drd <= sw; -- PINA=X"19" (0X39)
-- when PINB => IO_Drd <= In_PINB; -- PINB=X"16" (0X36)
when others => IO_Drd <= X"AA";
end case;
end if;
end process;
end microcontroleur_architecture;
La partie importante du code est :
line2_buffer(95 downto 92) => X"3",
line2_buffer(91 downto 88)=> s_2digits(7 downto 4),
line2_buffer(87 downto 84) => X"3",
line2_buffer(83 downto 80)=> s_2digits(3 downto 0),
car c'est ici que l'on transforme cmpt en deux caractères numériques affichables. La présence du X"3" est là pour transformer le chiffre en son code ASCII correspondant et le s_2digits est décomposé en deux parties. Ceci peut être représenté par le schéma ci-dessous :
Le fichier de contraintes pour la carte DE2-115 est en format CSV :
To,Direction,Location,I/O Bank,VREF Group,I/O Standard,Reserved CLK,Input,PIN_Y2,2,B2_N0,3.3-V LVTTL, SW[7],Input,PIN_AC25,5,B5_N2,2.5 V, SW[6],Input,PIN_AB26,5,B5_N1,2.5 V, SW[5],Input,PIN_AD26,5,B5_N2,2.5 V, SW[4],Input,PIN_AC26,5,B5_N2,2.5 V, SW[3],Input,PIN_AB27,5,B5_N1,2.5 V, SW[2],Input,PIN_AD27,5,B5_N2,2.5 V, SW[1],Input,PIN_AC27,5,B5_N2,2.5 V, SW[0],Input,PIN_AC28,5,B5_N2,2.5 V, Rst,Input,PIN_AB28,5,B5_N1,2.5 V, lcd_e,Output,PIN_AF15,4,B4_N2,3.3-V LVTTL, GPIO[12],Bidir,PIN_AD19,4,B4_N0,3.3-V LVTTL, lcd_rs,Output,PIN_AF16,4,B4_N2,3.3-V LVTTL, GPIO[10],Bidir,PIN_AC19,4,B4_N0,3.3-V LVTTL, GPIO[9],Bidir,PIN_AE15,4,B4_N2,3.3-V LVTTL, GPIO[8],Bidir,PIN_AD15,4,B4_N2,3.3-V LVTTL, lcd_db[7],Output,PIN_AE16,4,B4_N2,3.3-V LVTTL, GPIO[6],Bidir,PIN_AD21,4,B4_N0,3.3-V LVTTL, lcd_db[6],Output,PIN_Y16,4,B4_N0,3.3-V LVTTL, GPIO[4],Bidir,PIN_AC21,4,B4_N0,3.3-V LVTTL, lcd_db[5],Output,PIN_Y17,4,B4_N0,3.3-V LVTTL, GPIO[2],Bidir,PIN_AB21,4,B4_N0,3.3-V LVTTL, lcd_db[4],Output,PIN_AC15,4,B4_N2,3.3-V LVTTL, GPIO[0],Bidir,PIN_AB22,4,B4_N0,3.3-V LVTTL, led_out,Output,PIN_E21,7,B7_N0,2.5 V,
Exercice 4
[modifier | modifier le wikicode]Nous avons comme objectif d'utiliser deux FIFO : un par ligne à afficher. L'avantage est une utilisation de deux PORTs seulement, un par FIFO. L'inconvénient est qu'il va falloir bâtir un séquenceur capable de vider les FIFO pour réaliser l'affichage des lignes.
Réflexion :
- l'utilisation de deux PORTs comme indiqué dans l'énoncé peut n'être qu'une assertion de principe ! On peut utiliser un troisième PORT pour communiquer avec le séquenceur puisqu'il en faut un. Par exemple, utiliser le bit B0 de poids faible d'un troisième registre est possible : on le met à 1 pour dire au périphérique : ça y est les deux FIFO sont prêts à être utilisés pour être affichés, à toi de te débrouiller. On admet bien sûr que le processeur et son périphérique se tutoient.
- on peut aussi n'utiliser que deux PORTs et faire partir le séquenceur systématiquement quand les deux FIFO sont pleins
1°) Avant de mettre au point l'utilisation de deux lignes, nous allons nous contenter de la première ligne. Nous allons aussi utiliser un seul PORT qui écrira dans le FIFO. C'est donc le signal FIFO plein qui fera partir le séquenceur.
Indications :
- La partie donnée dans la figure ci-contre concerne l'écriture dans un FIFO. On rappelle qu'un FIFO est un registre dans lequel on écrit des données les unes à la suite des autres. La première donnée écrite sera la première donnée pouvant être lue. Si vous réalisez ce qui est en rouge dans la figure, vous aurez un FIFO complètement fonctionnel en écriture. Vous faites un programme qui écrit deux valeurs dans PORTB et elles partiront toutes les deux dans le FIFO. Si vous n'avez que cette écriture de disponible, vous ne pourrez jamais rien faire des valeurs qui sont dans le FIFO. Remarquez aussi que l'on a gardé la partie de l'exercice 3 qui affiche deux chiffres décimaux en deuxième ligne. Il faut compléter cette figure par un séquenceur destiné à lire les données disponibles dans le FIFO. Avant de vous attaquer à cette autre partie du problème, posez-vous un peu et essayez de bien comprendre ce qui est fait : c'est la partie la plus simple du problème que vous avez sous les yeux.
- Le séquenceur qui lit les 16 données du FIFO comporte 32 états. Il faut deux états pour lire une donnée car un état permet d'écrire dans line1_buffer tandis que l'autre dit au FIFO qu'une de ses données est lue. Pour ce séquenceur, seul le démarrage est conditionné à "full" qui signifie que le FIFO est rempli. Pour tous les autres états, il n'y a aucune condition de passage à l'état suivant, ..., puis à la fin on revient à l'état initial. Ce séquenceur est donc facile à écrire mais long (toutes les 32 transitions sont à réaliser dans un grand case when).
- Si le séquenceur est réalisé avec un compteur, cela permet de remplacer le grand case when par une simple incrémentation. Nous pouvons choisir un compteur de 5 bits : les 4 bits de poids fort feront le compteur jusqu'à 16 pour vider le FIFO tandis que le bit de poids faible fera le signal de lecture du FIFO. En effet après lecture d'un FIFO, il faut lui dire que sa donnée a été lue pour qu'il prépare la suivante.
- Le problème du signal FIFO plein est qu'il cesse d'être à 1 dès la première lecture. Vous allez utiliser ce signal et le mémoriser pendant tout le temps nécessaire à vider ce FIFO. C'est probablement là que réside la plus grande difficulté de conception de notre module. Prenez donc le temps de méditer cette figure. Nous avons dessiné des rectangles en pointillés parce qu'ils représentent des process (pour éviter les PORT MAP). Le process de comptage n'a rien de difficile. Le process qui maintien s_en_d à 1 tant que la lecture du FIFO n'est pas terminée n'est pas simple (enfin tout est relatif). Votre réflexion doit se faire en vous posant les questions :
- quelle condition fait passer s_en_d à 1 ? Réponse le passage de full à 1
- quelle condition fait passer s_en_d à 0 ? Réponse quand le compteur est arrivé au bout du comptage
- que se passe-t-il en dehors de ces conditions ? Réponse un simple maintien (mémorisation).
- Si les indications ci-dessus vous semblent insuffisantes, une autre version de la question 1°) plus conventionnelle est donnée un peu plus bas : question 1°-bis)
- Le FIFO est donné ci-dessous
-- Listing 4.20
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity fifo is
generic(
B: natural:=8; -- number of bits
W: natural:=4 -- number of address bits
);
port(
clk, reset: in std_logic;
rd, wr: in std_logic;
w_data: in std_logic_vector (B-1 downto 0);
empty, full: out std_logic;
r_data: out std_logic_vector (B-1 downto 0)
);
end fifo;
architecture arch of fifo is
type reg_file_type is array (2**W-1 downto 0) of
std_logic_vector(B-1 downto 0);
signal array_reg: reg_file_type;
signal w_ptr_reg, w_ptr_next, w_ptr_succ:
std_logic_vector(W-1 downto 0);
signal r_ptr_reg, r_ptr_next, r_ptr_succ:
std_logic_vector(W-1 downto 0);
signal full_reg, empty_reg, full_next, empty_next:
std_logic;
signal wr_op: std_logic_vector(1 downto 0);
signal wr_en: std_logic;
begin
--=================================================
-- register file
--=================================================
process(clk,reset)
begin
if (reset='1') then
array_reg <= (others=>(others=>'0'));
elsif (clk'event and clk='1') then
if wr_en='1' then
array_reg(to_integer(unsigned(w_ptr_reg)))
<= w_data;
end if;
end if;
end process;
-- read port
r_data <= array_reg(to_integer(unsigned(r_ptr_reg)));
-- write enabled only when FIFO is not full
wr_en <= wr and (not full_reg);
--=================================================
-- fifo control logic
--=================================================
-- register for read and write pointers
process(clk,reset)
begin
if (reset='1') then
w_ptr_reg <= (others=>'0');
r_ptr_reg <= (others=>'0');
full_reg <= '0';
empty_reg <= '1';
elsif (clk'event and clk='1') then
w_ptr_reg <= w_ptr_next;
r_ptr_reg <= r_ptr_next;
full_reg <= full_next;
empty_reg <= empty_next;
end if;
end process;
-- successive pointer values
w_ptr_succ <= std_logic_vector(unsigned(w_ptr_reg)+1);
r_ptr_succ <= std_logic_vector(unsigned(r_ptr_reg)+1);
-- next-state logic for read and write pointers
wr_op <= wr & rd;
process(w_ptr_reg,w_ptr_succ,r_ptr_reg,r_ptr_succ,wr_op,
empty_reg,full_reg)
begin
w_ptr_next <= w_ptr_reg;
r_ptr_next <= r_ptr_reg;
full_next <= full_reg;
empty_next <= empty_reg;
case wr_op is
when "00" => -- no op
when "01" => -- read
if (empty_reg /= '1') then -- not empty
r_ptr_next <= r_ptr_succ;
full_next <= '0';
if (r_ptr_succ=w_ptr_reg) then
empty_next <='1';
end if;
end if;
when "10" => -- write
if (full_reg /= '1') then -- not full
w_ptr_next <= w_ptr_succ;
empty_next <= '0';
if (w_ptr_succ=r_ptr_reg) then
full_next <='1';
end if;
end if;
when others => -- write/read;
w_ptr_next <= w_ptr_succ;
r_ptr_next <= r_ptr_succ;
end case;
end process;
-- output
full <= full_reg;
empty <= empty_reg;
end arch;
Et maintenant voici la solution complète :
La partie matérielle proposée ici est provisoire.
----------------------------------------------------------------------------------
-- Company:
-- Engineer:
--
-- Create Date: 08:57:48 08/26/2014
-- Design Name:
-- Module Name: microcontroleur - microcontroleur_architecture
-- Project Name:
-- Target Devices:
-- Tool versions:
-- Description:
--
-- Dependencies:
--
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity microcontroleur is
Port ( clk : in STD_LOGIC;
Rst : in STD_LOGIC;
sw : in STD_LOGIC_VECTOR (7 downto 0);
lcd_e : out std_logic;
lcd_rs : out std_logic;
lcd_rw : out std_logic;
lcd_db : out std_logic_vector(7 downto 4)
);
end microcontroleur;
architecture microcontroleur_architecture of microcontroleur is
--Registres et PORTs de l'ATTiny861
constant OCR1A : std_logic_vector(5 downto 0) := "101101";
constant OCR1B : std_logic_vector(5 downto 0) := "101100";
constant PORTA : std_logic_vector(5 downto 0) := "011011";
constant DDRA : std_logic_vector(5 downto 0) := "011010";
constant PINA : std_logic_vector(5 downto 0) := "011001";
constant PORTB : std_logic_vector(5 downto 0) := "011000";
constant DDRB : std_logic_vector(5 downto 0) := "010111";
constant PINB : std_logic_vector(5 downto 0) := "010110";
constant ADCH : std_logic_vector(5 downto 0) := "000101";
constant ADCL : std_logic_vector(5 downto 0) := "000100";
--Registres non présents dans l'ATTiny861
constant UDR : std_logic_vector(5 downto 0) := "000011";
constant UCSRA : std_logic_vector(5 downto 0) := "000010";
constant UCSRB : std_logic_vector(5 downto 0) := "000001";
component mcu_core is
Port (
Clk : in std_logic;
Rst : in std_logic; -- Reset core when Rst='1'
En : in std_logic; -- CPU stops when En='0', could be used to slow down cpu to save power
-- PM
PM_A : out std_logic_vector(15 downto 0);
PM_Drd : in std_logic_vector(15 downto 0);
-- DM
DM_A : out std_logic_vector(15 downto 0); -- 0x00 - xxxx
DM_Areal : out std_logic_vector(15 downto 0); -- 0x60 - xxxx (same as above + io-adr offset)
DM_Drd : in std_logic_vector(7 downto 0);
DM_Dwr : out std_logic_vector(7 downto 0);
DM_rd : out std_logic;
DM_wr : out std_logic;
-- IO
IO_A : out std_logic_vector(5 downto 0); -- 0x00 - 0x3F
IO_Drd : in std_logic_vector(7 downto 0);
IO_Dwr : out std_logic_vector(7 downto 0);
IO_rd : out std_logic;
IO_wr : out std_logic;
-- OTHER
OT_FeatErr : out std_logic; -- Feature error! (Unhandled part of instruction)
OT_InstrErr : out std_logic -- Instruction error! (Unknown instruction)
);
end component mcu_core;
--PM
component pm is
Port (
Clk : in std_logic;
rst : in std_logic; -- Reset when Rst='1'
-- PM
PM_A : in std_logic_vector(15 downto 0);
PM_Drd : out std_logic_vector(15 downto 0)
);
end component pm;
component dm is
Port ( clk : in STD_LOGIC;
addr : in STD_LOGIC_VECTOR (15 downto 0);
dataread : out STD_LOGIC_VECTOR (7 downto 0);
datawrite : in STD_LOGIC_VECTOR (7 downto 0);
rd : in STD_LOGIC;
wr : in STD_LOGIC);
end component dm;
component lcd16x2_ctrl is
generic (
CLK_PERIOD_NS : positive := 20); -- 50MHz
port (
clk : in std_logic;
rst : in std_logic;
lcd_e : out std_logic;
lcd_rs : out std_logic;
lcd_rw : out std_logic;
lcd_db : out std_logic_vector(7 downto 4);
line1_buffer : in std_logic_vector(127 downto 0); -- 16x8bit
line2_buffer : in std_logic_vector(127 downto 0));
end component lcd16x2_ctrl;
component fifo is
generic(
B: natural:=8; -- number of bits
W: natural:=4 -- number of address bits
);
port(
clk, reset: in std_logic;
rd, wr: in std_logic;
w_data: in std_logic_vector (B-1 downto 0);
empty, full: out std_logic;
r_data: out std_logic_vector (B-1 downto 0)
);
end component fifo;
signal PM_A : std_logic_vector(15 downto 0);
signal PM_Drd : std_logic_vector(15 downto 0);
-- DM
signal DM_A : std_logic_vector(15 downto 0); -- 0x00 - xxxx
signal DM_Areal : std_logic_vector(15 downto 0); -- 0x60 - xxxx (same as above + io-adr offset)
signal DM_Drd : std_logic_vector(7 downto 0);
signal DM_Dwr : std_logic_vector(7 downto 0);
signal DM_rd : std_logic;
signal DM_wr : std_logic;
-- IO
signal IO_A : std_logic_vector(5 downto 0); -- 0x00 - 0x3F
signal IO_Drd : std_logic_vector(7 downto 0);
signal IO_Dwr : std_logic_vector(7 downto 0);
signal IO_rd : std_logic;
signal IO_wr : std_logic;
signal IO_DrdA : std_logic_vector(7 downto 0);
signal IO_DrdB : std_logic_vector(7 downto 0);
signal s_2digits : std_logic_vector(7 downto 0);
signal s_r_data : std_logic_vector(7 downto 0);
signal s_wr, s_rd, s_full, s_en, s_en_d : std_logic;
signal s_line1_buffer, s_line2_buffer : std_logic_vector(127 downto 0);
signal cmpt : std_logic_vector(4 downto 0);
begin
core : mcu_core Port map (
Clk => clk,
Rst => Rst,
En => '1',
-- PM
PM_A => PM_A,
PM_Drd => PM_Drd,
-- DM
DM_A => DM_A,
DM_Areal => DM_Areal,
DM_Drd => DM_Drd,
DM_Dwr => DM_Dwr,
DM_rd => DM_rd,
DM_wr => DM_wr,
-- IO
IO_A => IO_A,
IO_Drd => IO_Drd,
IO_Dwr => IO_Dwr,
IO_rd => IO_rd,
IO_wr => IO_wr,
-- OTHER
OT_FeatErr => open,
OT_InstrErr => open
);
prgmem : pm port map (
Clk => clk,
Rst => '0',
-- PM
PM_A => PM_A,
PM_Drd => PM_Drd
);
datamem : dm port map (
clk => clk,
addr => DM_A,
dataread => DM_Drd,
datawrite => DM_Dwr,
rd => DM_rd,
wr => DM_wr
);
LCD : lcd16x2_ctrl port map (
clk => clk,
rst => Rst,
lcd_e => lcd_e,
lcd_rs => lcd_rs,
lcd_rw => lcd_rw,
lcd_db => lcd_db,
line1_buffer => s_line1_buffer,
line2_buffer(127 downto 96) => X"20202020",
line2_buffer(95 downto 92) => X"3",
line2_buffer(91 downto 88)=> s_2digits(7 downto 4),
line2_buffer(87 downto 84) => X"3",
line2_buffer(83 downto 80)=> s_2digits(3 downto 0),
line2_buffer(79 downto 0) => X"20202020202020202020"
);
s_rd <= cmpt(0); -- le poids faible sert à avertir le FIFO qu'on le lit
FIFO1: fifo port map(
clk => clk,
reset => Rst,
rd => s_rd,
wr => s_wr,
w_data => IO_Dwr,
empty => open, -- ce serait pas trop mal d'utiliser cette info
full => s_full,
r_data => s_r_data
);
-- IO write process
--
iowr: process(CLK)
begin
if (rising_edge(CLK)) then
if (IO_wr = '1') then
case IO_A is
-- addresses for tiny861 device (use io.h).
--
when PORTA => -- PORTA=X"1B" (0X3B)
s_2digits <= IO_Dwr;
when others =>
end case;
end if;
end if;
end process;
-- managing FIFO
s_wr <= IO_wr when (IO_A = PORTB) else '0'; -- write UART UDR
-- IO read process
--
iord: process(IO_rd,IO_A)
begin
-- addresses for tinyX6 device (use iom8.h).
--
if IO_rd = '1' then
case IO_A is
when PINA => IO_Drd <= sw; -- PINA=X"19" (0X39)
-- when PORTB => IO_Drd <= s_PORTB; -- PORTB=X"18" (0X38)
when others => IO_Drd <= X"AA";
end case;
end if;
end process;
-- sequenceur
-- gestion d'un bit à 1 pendant tout séquencement
en: process(clk) begin
if rising_edge(clk) then
if cmpt = "11111" then s_en_d <= '0';
elsif s_full='1' then s_en_d <='1';
else
s_en_d <= s_en_d;
end if;
end if;
end process;
-- un compteur comme séquenceur
seq: process(clk) begin
if rising_edge(clk) then
if s_en_d = '1' then
cmpt <= cmpt + 1;
end if;
end if;
end process;
buildingLine1: process(clk) begin
if rising_edge(clk) then
--if s_en_d = '1' then -- fonctionne mais charge deux fois de suite
if s_rd = '0' then --on est en train d'utiliser donc pas de lecture FIFO
case cmpt(4 downto 1) is
when "0000" => s_line1_buffer(127 downto 120) <= s_r_data;
when "0001" => s_line1_buffer(119 downto 112) <= s_r_data;
when "0010" => s_line1_buffer(111 downto 104) <= s_r_data;
when "0011" => s_line1_buffer(103 downto 96) <= s_r_data;
when "0100" => s_line1_buffer(95 downto 88) <= s_r_data;
when "0101" => s_line1_buffer(87 downto 80) <= s_r_data;
when "0110" => s_line1_buffer(79 downto 72) <= s_r_data;
when "0111" => s_line1_buffer(71 downto 64) <= s_r_data;
when "1000" => s_line1_buffer(63 downto 56) <= s_r_data;
when "1001" => s_line1_buffer(55 downto 48) <= s_r_data;
when "1010" => s_line1_buffer(47 downto 40) <= s_r_data;
when "1011" => s_line1_buffer(39 downto 32) <= s_r_data;
when "1100" => s_line1_buffer(31 downto 24) <= s_r_data;
when "1101" => s_line1_buffer(23 downto 16) <= s_r_data;
when "1110" => s_line1_buffer(15 downto 8) <= s_r_data;
when others => s_line1_buffer(7 downto 0) <= s_r_data;
end case;
end if;
end if;
end process;
end microcontroleur_architecture;
Un programme peut être :
#include <avr/io.h>
#define F_CPU 15000000UL
#include <util/delay.h>
char chaine[]=" Bonjour A tous";
void putLine(uint8_t no, char line[]) {
uint8_t i,etat=0;
for(i=0;i<16;i++) {
if (no==0) {
if (line[i]==0) etat=1;
if(etat==0) PORTB = line[i]; else PORTB = ' ';
} // if no==0
} // for
}
void effaceLine(uint8_t no) {
uint8_t i;
for(i=0;i<16;i++) {
if (no==0) {
PORTB = ' ';
} else {
PORTA = ' ';
} // if no == 0
} // for
}
int main(void) {
while (1) {
putLine(0,chaine);
PORTA = 0x18;
_delay_ms(1000);
}
return 0;
}
Nous avons deux sous-programmes incomplets car ils sont sensés gérer les deux lignes mais une seule est gérée par le matériel.
1°-bis) La façon de faire de la question 1°) est intéressante mais assez difficile pour des étudiants non spécialistes. La réalisation d'un séquenceur avec un compteur demande une réflexion importante et assez inhabituelle : les séquenceurs sont plutôt réalisés par des machines d'états. Lorsque nous avons décidé nous-même de donner cet exercice en examen à l'UTT, nous avons choisi de réaliser le séquenceur de manière plus classique comme le montre le schéma ci-contre.
2°) Réaliser l'interface complète des deux lignes.
3°) La façon de faire des deux questions précédentes consiste à prendre un module tout fait et à l'adapter à notre processeur en réalisant le moins de modifications possibles. Il serait plus confortable de réaliser une partie matérielle capable de reprendre les principales actions réalisées par la librairie C et de les implanter dans le matériel. Cela permettrait aux utilisateurs qui ont l'habitude d'utiliser une telle librairie de retrouver un peu leurs marques. Mais ce travail consiste alors, à ne prendre dans le module "lcd16x2_ctrl.vhd" que les parties intéressantes. Ceci nécessite donc beaucoup de temps : Avis aux amateurs.
Indications : Pour mieux comprendre le texte de la question donnons un peu plus de détails.
- en ce qui concerne l'initialisation, faut-il la rendre accessible par le processeur ou la laisser complètement indépendante ?
- réaliser le transfert 8 bits dans l'afficheur
- distinguer l'envoi d'une commande de l'envoi d'une donnée. Ceci peut être fait à l'aide de deux registres/PORTs :
- l'envoi d'une donnée se fait dans le PORTA (par exemple)
- l'envoi d'une commande se fait par le PORTB (par exemple)
- cette façon de faire ne nécessite pas obligatoirement de FIFO mais probablement un bit en lecture pour dire que l'envoi de donnée ou la commande est terminée. Certaines commandes sont relativement longues, comme l'effacement de l'écran.
4°) Encore plus difficile : réaliser un coprocesseur qui est capable d'exécuter certaines routines. Il faut alors prévoir un mécanisme de communication entre le Tiny861 et le coprocesseur pour que le premier puisse demander au deuxième de réaliser une routine précise.
Voir aussi
[modifier | modifier le wikicode]Tourelle pan/tilt et sonar
[modifier | modifier le wikicode]Petit rappel avant de commencer :
- le mot anglais pan signifie faire un panoramique horizontal
- le mot anglais tilt signifie incliner
Ce que nous allons traiter dans cette section n'est pas à proprement parler un shield. Mais nous avions prévenu : nous traitons dans ce chapitre de shields au sens large, c'est-à-dire tout ce qui peut être connecté à un Arduino en quelques secondes.
Ce sujet a été donné comme projet à des étudiants en 2016/2017. Il a consisté à réaliser une carte d'extension de la carte DE2-115 sur laquelle se trouvent une tourelle pan/tilt commandée par deux servomoteurs et un sonar HC-SR04. L'idée est d'explorer un peu le problème pour une éventuelle future utilisation en robotique mobile.
Présentation de la carte d'extension
[modifier | modifier le wikicode]Nous avons imposé aussi une carte MSP432 sur l'ensemble ainsi qu'un connecteur pour la manette Nunchuk déjà évoquée plusieurs fois dans ce livre.
Pour vous faire une idée plus précise, le compte-rendu de réalisation de deux binômes est disponible sur Internet dans le Wiki de l'IUT de troyes :
- CR de TO/Baczkowski et de Gaya/Gnagne (2016/2017)
Commande des servomoteurs
[modifier | modifier le wikicode]La commande de plusieurs servomoteurs n'est pas un problème dans un FPGA. Vous trouverez facilement du code VHDL pour cela. Nous n'avons pas choisi le code le plus compact mais l'essentiel est qu'il fonctionne :
Exercice 1
Réaliser un code VHDL qui utilise le fichier "servo.vhd" du ZIP ci-dessus ainsi que tous ses sous ensembles (servoclock.vhd, servotiming.vhd et servounit.vhd). On n'utilisera aucun processeur mais seulement un compteur 31 bits en lui prenant les 8 bits de poids fort pour réaliser la commande de servo. Autrement dit on fait un balayage horizontal en une quarantaine de secondes.
library IEEE;
use IEEE.std_logic_1164.all;
entity panTiltSonar is
port(
MCLK : in std_logic;
panSERVO : out std_logic
);
end panTiltSonar;
architecture arch_panTiltSonar of panTiltSonar is
component SERVO is
port(
MCLK : in std_logic;
nRST : in std_logic;
DATA : in std_logic_vector(7 downto 0);
SERVO : out std_logic
);
end component SERVO;
component cmpt31bits IS
PORT(
clk : IN STD_LOGIC;
cnt : OUT STD_LOGIC_VECTOR(30 DOWNTO 0)
);
END component cmpt31bits;
signal s_data8 : std_logic_vector(7 downto 0);
begin
panCmd : SERVO port map(
MCLK => MCLK,
nRST => '1',
Data => s_data8,
SERVO => panServo
);
comptage_lent : cmpt31bits PORT MAP (
clk => MCLK,
cnt(30 downto 23) => s_data8
);
end arch_panTiltSonar;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
ENTITY cmpt31bits IS
PORT(clk : IN STD_LOGIC;
cnt : OUT STD_LOGIC_VECTOR(30 DOWNTO 0));
END cmpt31bits;
ARCHITECTURE arch_cmpt31bits OF cmpt31bits IS
signal cmpt : std_logic_vector(30 downto 0);
BEGIN
process(clk) begin
if rising_edge(clk) then
cmpt <= cmpt + 1;
end if;
end process;
cnt <= cmpt;
END arch_cmpt31bits;
Et voici les contraintes correspondantes pour la carte de TO/Baczkowski :
To,Direction,Location,I/O Bank,VREF Group,I/O Standard,Reserved MCLK,Input,PIN_Y2,2,B2_N0,3.3-V LVTTL, panSERVO,Output,PIN_AC15,4,B4_N2,3.3-V LVTTL, tiltSERVO,Input,PIN_Y17,4,B4_N0,3.3-V LVTTL,
Lecture du sonar
[modifier | modifier le wikicode]Nous utilisons le célèbre sonar HC-SR04 qui peut être trouvé pour moins de 2 € assez facilement. Nous allons partir du code VHDL de Mike Field (Ce lien n'est plus disponible !!! Mais une partie du code peut être trouvée dans le compte-rendu cité ci-dessus). Ce lien semble indiquer qu'il fait fonctionner le HR-SR04 en 3,3 V puisqu'il n'y a pas de 5V sur les PMods ! Nous avons essayé en 3,3 V absolument sans succès, ce qui est parfaitement conforme à la documentation (alimentation entre 4,5 V et 5,5 V).
Nous l'avons fait fonctionner cependant en 5V d'alimentation et sans convertisseur de niveaux logiques, puis avec, pour des essais plus longs. En effet, la broche "echo" délivre du 5V ce qui n'est pas conseillé pour un FPGA. Ces essais montrent qu'il est possible de commander "Trig" en 3,3 V. La boche "echo" étant une entrée (pour le FPGA) un simple diviseur de tension avec des résistances 20k/10k est suffisant.
Tout ceci nous amène à envisager une utilisation avec les cartes terasic qui ont du 5V sur leurs connecteurs.
Exercice 2 : adaptation du code pour une de2-115
[modifier | modifier le wikicode]1°) Le code de Mike Field devra être changé :
- utilisation en 50 MHz au lieu des 100 MHz de la Basys 3
- les afficheurs de la de2-115 ne sont pas multiplexés contrairement à ceux de la Basys 3
- remplacement des types "unsigned" par des "std_logic_vector" et changer les librairies en conséquence
Ce dernier point n'est pas obligatoire mais nos étudiants de niveau 15 ne connaissent que les "std_logic_vector" et nous n'avons pas l'intention de leur enseigner plus sur le sujet.
Pour faire ces modifications vous pouvez repérer les deux process qui gèrent l'affichage et les commenter et les remplacer par un câblage du composant de l'exercice 2 du Corrigé du TP1. Ce composant nécessite bien sûr lui-même, le corrigé de l'exercice 1.
Avec ces petites modifications le code de Mike Field est parfaitement fonctionnel. Nous ne mettons pas la correction à disposition pour le moment car il faut bien laisser chercher nos étudiants.
Les contraintes pour la carte de TO/Baczkowski sont :
To,Direction,Location,I/O Bank,VREF Group,I/O Standard,Reserved MCLK,Input,PIN_Y2,2,B2_N0,3.3-V LVTTL, Unit7segs[6],Output,PIN_H22,6,B6_N0,2.5 V, Unit7segs[5],Output,PIN_J22,6,B6_N0,2.5 V, Unit7segs[4],Output,PIN_L25,6,B6_N1,2.5 V, Unit7segs[3],Output,PIN_L26,6,B6_N1,2.5 V, Unit7segs[2],Output,PIN_E17,7,B7_N2,2.5 V, Unit7segs[1],Output,PIN_F22,7,B7_N0,2.5 V, Unit7segs[0],Output,PIN_G18,7,B7_N2,2.5 V, Diz7segs[6],Output,PIN_U24,5,B5_N0,2.5 V, Diz7segs[5],Output,PIN_U23,5,B5_N1,2.5 V, Diz7segs[4],Output,PIN_W25,5,B5_N1,2.5 V, Diz7segs[3],Output,PIN_W22,5,B5_N0,2.5 V, Diz7segs[2],Output,PIN_W21,5,B5_N1,2.5 V, Diz7segs[1],Output,PIN_Y22,5,B5_N0,2.5 V, Diz7segs[0],Output,PIN_M24,6,B6_N2,2.5 V, sonar_trig,Output,PIN_AE16,4,B4_N2,3.3-V LVTTL, sonar_echo,Input,PIN_Y16,4,B4_N0,3.3-V LVTTL,
2°) Le code de Mike Field affiche incorrectement après 99. Pouvez-vous le modifier pour avoir 3 digits d'affichage en utilisant des compteurs BCD cascadés.
Utiliser un processeur Tiny861
[modifier | modifier le wikicode]Pour coordonner l'ensemble du travail de balayage de la tourelle pan/tilt et la lecture du sonar, nous avons décidé d'utiliser un processeur. Comme d'habitude, ce processeur sera enfoui dans le FPGA.
Nous avions comme objectif initial de réaliser un affichage sur écran VGA en niveaux de gris de profondeur à partir des données du sonar. Mais, les étudiants comme le tuteur ayant pris du retard nous nous rabattons sur un affichage de chacune des lignes horizontales balayées sur un afficheur lcd (sous forme de barregraphe). Cet afficheur est présent sur la carte DE2-115 que nous utilisons. Si vous n'en possédez pas, nous avons déjà présenté dans ce chapitre comment en utiliser un externe et très bon marché.
Schéma du montage réalisé
[modifier | modifier le wikicode]Si l'on veut connaître comment programmer notre Tiny, il nous faut savoir comment les périphériques sont connectés au processeur. C'est l'objet de la figure ci-contre (à venir si elle n'est pas encore là) où l'on discerne :
- l'utilisation du PORTB pour la gestion de l'afficheur lcd
- b7 .. b4 seront les données sur 4 bits
- b3 sera RW
- b2 sera RS
- b1 sera E
- l'utilisation du PORTA pour sortir sur des LEDs
- l'utilisation de DDRA pour commander la position horizontale de la tourelle (pan)
- l'utilisation de DDRB pour commander la position verticale de la tourelle (tilt)
- l'utilisation de PINB en lecture pour lire la distance en cm
- l'utilisation de DDRB en lecture pour avoir en poids faible le status du sonar
Ressource
[modifier | modifier le wikicode]Nous vous donnons la ressource de départ sur notre site :
Tout y est réalisé pour être conforme au schéma que l'on vient de présenter.
L'hébergement de mon site perso se termine le 5 septembre 2023 ! A ce jour je n'ai pas encore décidé comment je vais gérer ce problème dont je ne suis pas à l'origine. Il en résulte que le lien panTiltSonarTiny.zip et l'ensemble des corrections qui utilisent mon site perso seront indisponibles à partir de cette date pour tout ce chapitre. SergeMoutou (discuter) |
La ressource sonar étudiée précédemment a été légèrement modifiée. Elle a, d'autre part, été interfacée au processeur un peu à la va-vite : elle est quasi indépendante. Le processeur est seulement capable de lire son état et son résultat (sur 8 bits seulement). Elle fonctionne toute seule sans arrêt. Nous essayerons de modifier cet état des choses avec les futurs projets.
Code de départ
[modifier | modifier le wikicode]Le code de départ est donné pour que vous n'ayez pas tout à faire. Ce code vous montre comment créer un caractère spécifique pour l'afficheur LCD et aussi comment l'utiliser.
// essai.c
//
// Copyright 2014 Michel Doussot <michel@mustafar>
// Modifié 2017 Serge MOUTOU
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
// MA 02110-1301, USA.
// version modifiée par Serge MOUTOU (6 mars 2007)
#include <avr/io.h>
#define RS 0x04
#define E 0x02
#define RW 0x08
#define DATAS 0xF0
// unite approximative 2us
void delai(unsigned long int delai) {
volatile long int i=0;
for(i=0;i<delai;i+=1);
}
// portB :
// b7 b6 b5 b4 b3 b2 b1 b0
// D3 D2 D1 D0 RW RS E CE0
void rs_haut(void) {
PORTB = PORTB | RS;
}
void rs_bas(void) {
PORTB = PORTB & ~RS;
}
void rw_haut(void) {
PORTB = PORTB | RW;
}
void rw_bas(void) {
PORTB = PORTB & ~RW;
}
void e_haut(void) {
PORTB = PORTB | E;
delai(8);
}
void e_bas(void) {
PORTB = PORTB & ~E;
delai(8);
}
void e_puls(void) {
e_haut();
e_bas();
}
void ecris_4(unsigned char valeur) {
unsigned char v;
v = (valeur << 4) & DATAS;
PORTB = PORTB & ~DATAS ;
PORTB = PORTB | v ;
e_puls();
}
void ecris_8(unsigned char valeur) {
unsigned char v;
v = valeur & DATAS;
PORTB = PORTB & ~DATAS ;
PORTB = PORTB | v ;
e_puls();
v = (valeur << 4) & DATAS;
PORTB = PORTB & ~DATAS ;
PORTB = PORTB | v ;
e_puls();
}
void setup() {
PORTB = 0;
delai(6000);
ecris_4(0x03);
delai(1600);
ecris_4(0x03);
delai(800);
ecris_4(0x03);
delai(800);
ecris_4(0x02);
delai(40);
ecris_4(0x02);
ecris_4(0x08);
delai(40);
ecris_4(0x00);
ecris_4(0x06);
delai(40);
ecris_4(0x00);
ecris_4(0x0C);
delai(40);
ecris_4(0x00);
ecris_4(0x01);
delai(800);
}
void writecar(char car) {
rs_haut();
ecris_8((unsigned char)car);
}
void writestr(char *chaine) {
rs_haut();
while (*chaine) {
ecris_8((unsigned char)*chaine++);
}
}
void command (uint8_t value) {
rs_bas();
ecris_8(value);
}
#define LCD_SETDDRAMADDR 0x80
void setCursor(uint8_t col, uint8_t row) {
col = col & 0x0F; // %16
row = row & 0x01; // %2
command(LCD_SETDDRAMADDR | (col + 0x40*row));
delai(100);
}
#define LCD_CLEARDISPLAY 0x01
void clearScreen() {
command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
delai(1000); // this command takes a long time!
}
#define LCD_SETCGRAMADDR 0x40
// Allows us to fill the first 8 CGRAM locations
// with custom characters
void createChar(uint8_t location, uint8_t charmap[]) {
uint8_t i;
location &= 0x7; // we only have 8 locations 0-7
command(LCD_SETCGRAMADDR | (location << 3));
for (i=0; i<8; i++) {
writecar((char)charmap[i]);delai(100);
}
}
uint8_t heart[8] = { // from Arduino Library
0b00000,
0b01010,
0b11111,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000
};
int main(void) {
unsigned char tmp;
// setup
setup();
tmp = 0;
createChar(0,heart);
clearScreen();
writestr("Hello Microsoft ");
setCursor(2,1);
writestr("Hello Linux");writecar(0);
// loop
while (1) {
PORTA = tmp;
delai(100000);
tmp += 1;
}
return 0;
}
Tout le monde aura reconnu un cœur comme caractère spécial qui semble indiquer notre préférence entre Linux et Windows.
Code final
[modifier | modifier le wikicode]L'objectif de ce code est de gérer :
- le balayage horizontal
- le balayage vertical
- de synchroniser les deux balayages
- de gérer le sonar
- de créer les nouveaux caractères du barregraphe
- d'afficher les résultats sur l'écran LCD
Utilisation d'un capteur de couleur TCS3200
[modifier | modifier le wikicode]Le composant TCS3200 est un capteur de couleur qui se trouve facilement tout monté sur une petite carte avec 4 leds pour moins de 2 € (en 2018). Nous avons décidé d'examiner la possibilité d'utilisation de ce type de capteurs pour un suivi de lignes en robotique.
Ce capteur est composé de 8x8 photodiodes :
- 16 sont précédées par un filtre qui enlève le bleu
- 16 sont précédées par un filtre qui enlève le rouge
- 16 sont précédées par un filtre qui enlève le vert
- 16 ne sont pas filtrées
Il sort de ces photodiodes un courant qui est converti intérieurement en fréquence. En clair ce que nous avons à mesurer est une fréquence ou une période.
- Fonctionnement S0, S1
S0 S1 Echelle de sortie de fréquence 0 0 Éteint 0 1 2% 1 0 20% 1 1 100%
Nous avons décidé d'utiliser le mode 100% pour nos premiers essais. S0 et S1 seront donc positionnés à 1 par l'Arduino ou le FPGA.
- Fonctionnement S2, S3
S2 S3 Type de filtre lumineux 0 0 Rouge 0 1 Bleu 1 0 Pas de filtre 1 1 Vert
Nous avons décidé d'utiliser le mode sans filtre de couleur pour nos premiers essais.
Première utilisation avec un Arduino
[modifier | modifier le wikicode]Ce capteur supporte des tensions d'alimentation allant de 2,5 V à 5,5 V. Il peut donc être utilisé avec un Arduino sans problème. Nous avons facilement trouvé un petit programme sur Internet que nous avons modifié
#define S0 4
#define S1 5
#define S2 6
#define S3 7
#define sensorOut 8
int frequency = 0;
void setup() {
pinMode(S0, OUTPUT);
pinMode(S1, OUTPUT);
pinMode(S2, OUTPUT);
pinMode(S3, OUTPUT);
pinMode(sensorOut, INPUT);
digitalWrite(S0,HIGH);
//digitalWrite(S1,LOW);
digitalWrite(S1,HIGH);
Serial.begin(9600);
}
void loop() {
//digitalWrite(S2,LOW);
digitalWrite(S2,HIGH);
digitalWrite(S3,LOW);
frequency = pulseIn(sensorOut, LOW);
Serial.print("T= ");
Serial.print(frequency);
Serial.println(" ");
delay(1000);
}
La lecture du code source montre comment tout cela est connecté.
Portage sur FPGA
[modifier | modifier le wikicode]Nous avons utilisé une carte Nexys3 avec un processeur enfoui ATMega16 largement utilisé dans ce cours.
La partie matérielle est réalisée par le code suivant :
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.std_logic_arith.all;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity pulseIn is
port(
pulse, clk : in std_logic;
-- TOV : out std_logic; --Timer overflow
timer : out std_logic_vector(7 downto 0));
end pulseIn;
architecture arch_pulseIn of pulseIn is
-- declaration des composants
component filter is port(
clk, pulse : in std_logic;
noicelessPulse : out std_logic);
end component filter;
component edge_detect is
port(
clk, reset: in std_logic;
level: in std_logic;
en_timer : out std_logic;
tick: out std_logic
);
end component edge_detect;
--declarations des signaux
signal s_Pulse, s_tick, s_en_timer : std_logic;
signal s_timer : std_logic_vector(19 downto 0);
begin
-- câblage des composants
i1: filter port map(
clk => clk,
pulse => pulse,
noicelessPulse => s_Pulse);
rising: edge_detect port map(
clk => clk,
reset => '0',
level => s_Pulse,
en_timer => s_en_timer,
tick => s_tick);
-- timer
process(clk) begin
if rising_edge(clk) then
if s_tick ='1' then
s_timer <= (others =>'0');
timer <= s_timer(11 downto 4);
elsif s_en_timer='1' then
s_timer <= s_timer + 1;
end if;
end if;
end process;
end arch_pulseIn;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity filter is port(
clk, pulse : in std_logic;
noicelessPulse : out std_logic);
end filter;
architecture arch_filter of filter is
signal filteredPulse,FP : std_logic;
signal datashiftreg : std_logic_vector(7 downto 0);
begin
-- partie combinatoire
with filteredPulse select
FP <= datashiftreg(7) and datashiftreg(6) and datashiftreg(5) and datashiftreg(4)
and datashiftreg(3) and datashiftreg(2) and datashiftreg(1) and datashiftreg(0) when '0',
datashiftreg(7) or datashiftreg(6) or datashiftreg(5) or datashiftreg(4)
or datashiftreg(3) or datashiftreg(2) or datashiftreg(1) or datashiftreg(0) when others;
-- partie sequentielle
process (clk) begin
if rising_edge(clk) then
-- registre a decalage
datashiftreg <= pulse& datashiftreg(7 downto 1);
filteredPulse <= FP;
end if;
end process;
noicelessPulse <= filteredPulse;
end arch_filter;
-- Listing 5.3 : Pong P. Chu
-- FPGA Prototyping by VHDL Examples
-- Wiley 2008
-- en_timer added 28th Jan 2019
library ieee;
use ieee.std_logic_1164.all;
entity edge_detect is
port(
clk, reset: in std_logic;
level: in std_logic;
en_timer : out std_logic;
tick: out std_logic
);
end edge_detect;
architecture moore_arch of edge_detect is
type state_type is (zero, edge, one);
signal state_reg, state_next: state_type;
begin
-- state register
process(clk,reset)
begin
if (reset='1') then
state_reg <= zero;
elsif (clk'event and clk='1') then
state_reg <= state_next;
end if;
end process;
-- next-state/output logic
process(state_reg,level)
begin
state_next <= state_reg;
tick <= '0';
en_timer <= '0';
case state_reg is
when zero=>
if level= '1' then
state_next <= edge;
end if;
when edge =>
tick <= '1';
if level= '1' then
state_next <= one;
else
state_next <= zero;
end if;
when one =>
en_timer <= '1';
if level= '0' then
state_next <= zero;
end if;
end case;
end process;
end moore_arch;
Comme vous pouvez le remarquer dans ce code VHDL, nous avons décidé d'utiliser un petit filtrage sur les signaux envoyés par le capteur. (Pour tout vous dire nous n'avons pas essayé sans). C'est le module filter qui s'occupe de cela : il sort un 1 quand il y a au moins 8 1 de reçu consécutivement. Une fois le 1 sorti il faut huit 0 de suite pour le passer à 0.
L'autre module est destiné à la détection de fronts montants. Il sort deux signaux :
- tick : qui sert à la gestion de la mise à zéro du timer et à la sauvegarde des valeurs intéressantes dans un registre 8 bits.
- en_timer qui sert à autoriser le comptage du timer
Le module complet met en série ces deux modules et gère un timer.
Ce module est donc connecté au SOC ATMega18 dans lequel tourne le programme suivant :
#include <avr/io.h>
#undef F_CPU
#define F_CPU 50000000UL
#include "util/delay.h"
int main() {
uint8_t period;
// init
// _____________________________________
// | X | X | OE | S0 | S1 | X | S2 | S3 |
// _____________________________________
// | O | O | 0 | 1 | 1 | 1 | 1 | 0 | = 0x1E
// --------------------------------------
PORTB = 0x1E;
_delay_ms(10);
// loop
while(1) {
period = PINB;
PORTC = period;
_delay_ms(1000);
}
return 0;
}
Voici la connexion correspondante permettant d'expliquer le code (information identique à la figure ci-dessus) :
PORTB de ATMega16 | X | X | OE | S0 | S1 | X | S2 | S3 |
---|---|---|---|---|---|---|---|---|
FPGA Pin (connecteur JA) | NC | NC | JA<5> | JA<4> | JA<3> | JA<2> | JA<1> | JA<0> |
TCS3200 | - | - | OE | S0 | S1 | OUT | S2 | S3 |
- X signifie que les deux bits de poids fort du PORTB n'agissent pas sur le capteur
- NC désigne : Non Connecté
- OUT est une sortie du capteur, donc une entrée du FPGA. Il est donc normal qu'elle ne soit pas présente sur le PORTB de l'ATMega16. C'est une entrée spécifique qui est utilisée directement dans le module pulsin.vhd
- les - sont là car le capteur n'a que 6 broches pour être commandé et lu (plus les deux VCC et GND)
Voir aussi
[modifier | modifier le wikicode]- Manuel d'utilisation du capteur de couleur TCS3200 (fr)
- Youtube:Arduino Color Sensing Tutorial (en)
- Arduino Color Sensing Tutorial (en)
- Chapitre sur l'Arduino dans la Wikiversité
Utilisation d'une caméra OV7670
[modifier | modifier le wikicode]Les caméras OV7670 sont des caméras très bon marché : entre 5 € et 8 € sur Internet au moment de l'écriture de ces lignes. Elles sont configurables par un protocole très proche de l'i2c mais l'on doit récupérer soi-même les données du capteur par paquets de 8 bits.
Ce capteur vidéo n'est pas à proprement parler un shield. Il est d'autre part difficile à utiliser avec un Arduino dans sa version simple (sans mémoire externe). Il n'empêche qu'il est facile de trouver des vidéos et du code qui met en œuvre cette caméra OV7670avec des Arduino.
Une première utilisation
[modifier | modifier le wikicode]Nous avons pris l'habitude dans ce chapitre de tester les shields avec des processeurs en 3,3 V. Nous allons déroger à la règle ici car ces processeurs ne sont pas très adaptés à des essais de caméras. Cela ne veut pas dire que ce n'est pas possible puisqu'il est facile de trouver sur internet des exemples avec un processeur 32 bits.
Nous allons plutôt essayer un code VHDL tout fait pour commencer : il s'agit de saisir une image et de l'envoyer directement sur un écran vidéo.
Un code de ce genre peut être trouvé sous le titre "Digital Camera: OV7670 CMOS camera + DE2-115 FPGA board + LandTiger 2.0 board". En bas de la page en question se trouvent les liens pour télécharger les diverses versions du projet. Nous avons testé la première version. Pour cela, nous avons bien sûr passé plus de temps à câbler la caméra qu'à essayer ce code qui est absolument fonctionnel tout de suite. En effet nous disposons d'une carte DE2-115.
Aucun autre commentaire à part que la caméra se branche évidemment sur le connecteur d'extension. Pour savoir comment, il vous faut comparer la documentation du connecteur avec les contraintes pour les diverses entrées sorties de la caméra. C'est long mais pas difficile.
Le premier auteur qui semble avoir réalisé un code VHDL pour cette caméra bon marché semble être Mike Field. Son site a changé ou disparu depuis l'écriture de ces lignes mais il est assez facile de retrouver son code repris par d'autres : dans ce Github par exemple.
Une application simple
[modifier | modifier le wikicode]Le code de la section précédente est gourmand en mémoire interne au FPGA. Pour un cyclone IV 115 ce n'est pas un problème, mais nous avons l'intention d'utiliser des FPGA plus petits pour résoudre d'ailleurs des problèmes simples. Notre objectif est simplement de réaliser un suivi de lignes avec un Robot à l'aide de cette caméra.
!!!Doc en vrac à ne pas lire!!!
[modifier | modifier le wikicode]Un problème de résolution de la caméra par rapport à la mémoire FPGA utilisée dans un autre projet a été réglé par le mail ci-dessous. La documentation de la caméra est tellement opaque que nous laissons ce mail ici en VRAC pour le moment :
Je ne sais pas si tu es toujours avec ta caméra ? Peux-tu essayer de passer de registre d'adresse 3E à 0x19 pour changer la résolution pour être en accord avec ta mémoire. Autrement, pour faire encore plus petit, met-le à 0x1A Cela se fait avec : when x"05" => sreg <= x"3E19"; -- QVGA YUV tout petit x2 ou when x"05" => sreg <= x"3E1A"; -- QQVGA YUV tout petit x4 dans le case address du fichier ov7670_registers.vhd Le problème est qu'avec ces valeurs tu passes en mode YUV ce qui en principe n'est pas gênant pour faire du noir et blanc, mais peut te surprendre. Autrement dans ov7670_capture.vhd je fabrique actuellement mon dout avec : -- version 4 bits de niveaux de gris : dout <= d_latch(15 downto 12) & d_latch(10 downto 7) & d_latch(4 downto 1); -- version (8 bits ?) de niveaux de gris : -- dout <= "0000" & d_latch(10 downto 3); Tu prends ce qui n'est pas commenté. Il me semble que les commentaires sont faux. Pour faire ton seuillage, je pense que le mieux est de travailler dans RGB.vhd. Moi actuellement j'ai : begin -->> original : -- R <= Din(11 downto & Din(11 downto when Nblank='1' else "00000000"; -- G <= Din(7 downto 4) & Din(7 downto 4) when Nblank='1' else "00000000"; -- B <= Din(3 downto 0) & Din(3 downto 0) when Nblank='1' else "00000000"; --<< --> version 4 bits de niveaux de gris : -- R <= Din(7 downto 4) & "0000" when Nblank='1' else "00000000"; -- G <= Din(7 downto 4) & "0000" when Nblank='1' else "00000000"; -- B <= Din(7 downto 4) & "0000" when Nblank='1' else "00000000"; --<< --> version (8 bits ?) de niveaux de gris : R <= Din(7 downto 0) when Nblank='1' else "00000000"; G <= Din(7 downto 0) when Nblank='1' else "00000000"; B <= Din(7 downto 0) when Nblank='1' else "00000000"; --<< end Behavioral; A mon avis, pour le seuillage, il te faudra faire un truc du genre : R <= Din(7) OR Din(6) OR Din(5) when Nblank='1' else '0'; avec un Din(4) en plus dans le OU, ou seulement Din(7) OR Din(6), c'est là que tu interviens ! et même chose pour G et B... Bon WE A+
Capteur de débit optique
[modifier | modifier le wikicode]Les capteurs de débits optiques sont utilisés principalement dans les souris optiques. Ils sont donc devenus bon marché. Leur domaine d'application s'est récemment élargi aux Drones. On n'imagine pas, bien sûr, une optique à ras du sol pour le drone qui évolue en altitude : l'optique n'est donc certainement pas une optique de souris. On a donc développé des optiques autour des composants utilisés dans les souris. Voila ce que nous désirons étudier aujourd'hui.
Présentation de notre capteur
[modifier | modifier le wikicode]Nous allons utiliser un capteur architecturé autour du circuit ADNS 3080. Ce circuit est capable de prendre une image de 30x30 pixels et de la comparer à l'image précédente pour en déduire le déplacement. Ce calcul est réalisé avec un processeur embarqué de 32 bits.
La photo ci-contre présente le capteur seul. Mais si vous voulez voir à quoi ressemble le capteur complet cliquez ici. Ce composant associé à une optique peut être trouvé aux alentours de 7 € (Oct. 2018).
Comme d'habitude nous allons commencer par faire fonctionner tout cela dans des microcontrôleurs 3,3 V. Nous allons utiliser la carte Tiva C series de Texas instrument.
Code de démarrage
[modifier | modifier le wikicode]Nous avons trouvé sur Internet du code destiné à un Arduino. Il ne nous a pas été très difficile de le porter pour une utilisation avec Energia et notre ARM 32 bits.
TIVA C Pin | GND | NC | 3,3 V | PB5 | PB6 | PB7 | PB4 | PB3 | NC | NC |
---|---|---|---|---|---|---|---|---|---|---|
CJMCU | GND | 5V | 3V | NCS | MISO | MOSI | SCLK | RST | NPD | LED |
#include "SPI.h"
// Original Code by: Simon Winder
// https://github.com/impressivemachines/Arduino
// https://www.youtube.com/user/robotbugs
// small changes by: Jan Neumann aka. Neumi
// https://github.com/Neumi
// https://www.youtube.com/user/NeumiElektronik
// All code is published unter MIT license. Feel free to use!
/*************************
* 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)
*
**************************/
//#define PIN_SS PB_5
#define PIN_MISO PB_6
#define PIN_MOSI PB_7
#define PIN_SCK PB_4
#define PIN_MOUSECAM_RESET PB_3
#define PIN_MOUSECAM_CS PB_5
#define ADNS3080_PIXELS_X 30
#define ADNS3080_PIXELS_Y 30
#define ADNS3080_PRODUCT_ID 0x00
#define ADNS3080_REVISION_ID 0x01
#define ADNS3080_MOTION 0x02
#define ADNS3080_DELTA_X 0x03
#define ADNS3080_DELTA_Y 0x04
#define ADNS3080_SQUAL 0x05
#define ADNS3080_PIXEL_SUM 0x06
#define ADNS3080_MAXIMUM_PIXEL 0x07
#define ADNS3080_CONFIGURATION_BITS 0x0a
#define ADNS3080_EXTENDED_CONFIG 0x0b
#define ADNS3080_DATA_OUT_LOWER 0x0c
#define ADNS3080_DATA_OUT_UPPER 0x0d
#define ADNS3080_SHUTTER_LOWER 0x0e
#define ADNS3080_SHUTTER_UPPER 0x0f
#define ADNS3080_FRAME_PERIOD_LOWER 0x10
#define ADNS3080_FRAME_PERIOD_UPPER 0x11
#define ADNS3080_MOTION_CLEAR 0x12
#define ADNS3080_FRAME_CAPTURE 0x13
#define ADNS3080_SROM_ENABLE 0x14
#define ADNS3080_FRAME_PERIOD_MAX_BOUND_LOWER 0x19
#define ADNS3080_FRAME_PERIOD_MAX_BOUND_UPPER 0x1a
#define ADNS3080_FRAME_PERIOD_MIN_BOUND_LOWER 0x1b
#define ADNS3080_FRAME_PERIOD_MIN_BOUND_UPPER 0x1c
#define ADNS3080_SHUTTER_MAX_BOUND_LOWER 0x1e
#define ADNS3080_SHUTTER_MAX_BOUND_UPPER 0x1e
#define ADNS3080_SROM_ID 0x1f
#define ADNS3080_OBSERVATION 0x3d
#define ADNS3080_INVERSE_PRODUCT_ID 0x3f
#define ADNS3080_PIXEL_BURST 0x40
#define ADNS3080_MOTION_BURST 0x50
#define ADNS3080_SROM_LOAD 0x60
#define ADNS3080_PRODUCT_ID_VAL 0x17
void mousecam_reset()
{
digitalWrite(PIN_MOUSECAM_RESET,HIGH);
delay(1); // reset pulse >10us
digitalWrite(PIN_MOUSECAM_RESET,LOW);
delay(35); // 35ms from reset to functional
}
int mousecam_init()
{
pinMode(PIN_MOUSECAM_RESET,OUTPUT);
pinMode(PIN_MOUSECAM_CS,OUTPUT);
digitalWrite(PIN_MOUSECAM_CS,HIGH);
mousecam_reset();
int pid = mousecam_read_reg(ADNS3080_PRODUCT_ID);
if(pid != ADNS3080_PRODUCT_ID_VAL)
return -1;
// turn on sensitive mode
mousecam_write_reg(ADNS3080_CONFIGURATION_BITS, 0x19);
return 0;
}
void mousecam_write_reg(int reg, int val)
{
digitalWrite(PIN_MOUSECAM_CS, LOW);
SPI.transfer(reg | 0x80);
SPI.transfer(val);
digitalWrite(PIN_MOUSECAM_CS,HIGH);
delayMicroseconds(50);
}
int mousecam_read_reg(int reg)
{
digitalWrite(PIN_MOUSECAM_CS, LOW);
SPI.transfer(reg);
delayMicroseconds(75);
int ret = SPI.transfer(0xff);
digitalWrite(PIN_MOUSECAM_CS,HIGH);
delayMicroseconds(1);
return ret;
}
struct MD
{
byte motion;
char dx, dy;
byte squal;
word shutter;
byte max_pix;
};
void mousecam_read_motion(struct MD *p)
{
digitalWrite(PIN_MOUSECAM_CS, LOW);
SPI.transfer(ADNS3080_MOTION_BURST);
delayMicroseconds(75);
p->motion = SPI.transfer(0xff);
p->dx = SPI.transfer(0xff);
p->dy = SPI.transfer(0xff);
p->squal = SPI.transfer(0xff);
p->shutter = SPI.transfer(0xff)<<8;
p->shutter |= SPI.transfer(0xff);
p->max_pix = SPI.transfer(0xff);
digitalWrite(PIN_MOUSECAM_CS,HIGH);
delayMicroseconds(5);
}
// pdata must point to an array of size ADNS3080_PIXELS_X x ADNS3080_PIXELS_Y
// you must call mousecam_reset() after this if you want to go back to normal operation
int mousecam_frame_capture(byte *pdata)
{
mousecam_write_reg(ADNS3080_FRAME_CAPTURE,0x83);
digitalWrite(PIN_MOUSECAM_CS, LOW);
SPI.transfer(ADNS3080_PIXEL_BURST);
delayMicroseconds(50);
int pix;
byte started = 0;
int count;
int timeout = 0;
int ret = 0;
for(count = 0; count < ADNS3080_PIXELS_X * ADNS3080_PIXELS_Y; )
{
pix = SPI.transfer(0xff);
delayMicroseconds(10);
if(started==0)
{
if(pix&0x40)
started = 1;
else
{
timeout++;
if(timeout==100)
{
ret = -1;
break;
}
}
}
if(started==1)
{
pdata[count++] = (pix & 0x3f)<<2; // scale to normal grayscale byte range
}
}
digitalWrite(PIN_MOUSECAM_CS,HIGH);
delayMicroseconds(14);
return ret;
}
void setup()
{
// pinMode(PIN_SS,OUTPUT);
pinMode(PIN_MISO,INPUT);
pinMode(PIN_MOSI,OUTPUT);
pinMode(PIN_SCK,OUTPUT);
SPI.begin();
SPI.setClockDivider(SPI_CLOCK_DIV32);
SPI.setDataMode(SPI_MODE3);
SPI.setBitOrder(MSBFIRST);
Serial.begin(9600);
if(mousecam_init()==-1)
{
Serial.println("Mouse cam failed to init");
while(1);
}
}
char asciiart(int k)
{
static char foo[] = "WX86*3I>!;~:,`. ";
return foo[k>>4];
Serial.begin(9600);
}
byte frame[ADNS3080_PIXELS_X * ADNS3080_PIXELS_Y];
void loop()
{
#if 1
// if enabled this section grabs frames and outputs them as ascii art
if(mousecam_frame_capture(frame)==0)
{
int i,j,k;
for(i=0, k=0; i<ADNS3080_PIXELS_Y; i++)
{
for(j=0; j<ADNS3080_PIXELS_X; j++, k++)
{
Serial.print(asciiart(frame[k]));
Serial.print(' ');
}
Serial.println();
}
}
Serial.println();
delay(250);
#else
// if enabled this section produces a bar graph of the surface quality that can be used to focus the camera
// also drawn is the average pixel value 0-63 and the shutter speed and the motion dx,dy.
int val = mousecam_read_reg(ADNS3080_PIXEL_SUM);
MD md;
mousecam_read_motion(&md);
/*
for(int i=0; i<md.squal/4; i++)
Serial.print('*');
Serial.print(' ');
Serial.print((val*100)/351);
Serial.print(' ');
*/
//Serial.print(md.shutter); Serial.print(" (");
int X = (int)md.dx;
int Y = (int)md.dy;
if(X > -1) Serial.print("+");
if(X < 0) Serial.print("-");
if(abs(X) < 10) Serial.print("0");
Serial.print(abs(X));
Serial.print(',');
if(Y > -1) Serial.print("+");
if(Y < 0) Serial.print("-");
if(abs(Y) < 10) Serial.print("0");
Serial.println(abs(Y));
/*
Serial.print((int)md.dx); Serial.print(',');
Serial.println((int)md.dy); //Serial.println(')');
// Serial.println(md.max_pix);
*/
delay(10);
#endif
}
Portage dans un FPGA
[modifier | modifier le wikicode]Le FPGA choisi est un FPGA spartan6 LX9 monté sur une carte achetée en Chine pour environ 20 €. Elle nécessite naturellement d'avoir à disposition un programmateur Xilinx
Partie matérielle
[modifier | modifier le wikicode]L'interaction avec ce type de capteur est en SPI. Nous avons déjà utilisé le SPI mais plutôt en esclave. Le travail pour réaliser un maître SPI est déjà réalisé mais sera présenté un peu plus loin.
La gestion du SPI sera donc réalisée comme d'habitude par notre SOC ATMega16.
La seule chose à faire est donc de porter une librairie quelconque qui fonctionne avec le SPI, ce que nous avons utilisé avec le microcontrôleur d'essai utilisé dans la section précédente.
Partie logicielle
[modifier | modifier le wikicode]Le code donné ici est complet pour capturer une image et l'envoyer à la vitesse de 19200 bauds (avec quartz 50MHz) par la liaison série. Ce que vous obtenez dans votre hyperterminal est assez difficile à interpréter mais vous voyez des changements avec la lumière.
La capture du déplacement est aussi fonctionnelle (rappelons que ce capteur est destiné aux souris informatiques). Elle nous servira certainement plus tard aux calculs de déplacement d'un robot mobile.
Voici une librairie en C :
#define ADNS3080_PIXELS_X 30
#define ADNS3080_PIXELS_Y 30
#define ADNS3080_PRODUCT_ID 0x00
#define ADNS3080_REVISION_ID 0x01
#define ADNS3080_MOTION 0x02
#define ADNS3080_DELTA_X 0x03
#define ADNS3080_DELTA_Y 0x04
#define ADNS3080_SQUAL 0x05
#define ADNS3080_PIXEL_SUM 0x06
#define ADNS3080_MAXIMUM_PIXEL 0x07
#define ADNS3080_CONFIGURATION_BITS 0x0a
#define ADNS3080_EXTENDED_CONFIG 0x0b
#define ADNS3080_DATA_OUT_LOWER 0x0c
#define ADNS3080_DATA_OUT_UPPER 0x0d
#define ADNS3080_SHUTTER_LOWER 0x0e
#define ADNS3080_SHUTTER_UPPER 0x0f
#define ADNS3080_FRAME_PERIOD_LOWER 0x10
#define ADNS3080_FRAME_PERIOD_UPPER 0x11
#define ADNS3080_MOTION_CLEAR 0x12
#define ADNS3080_FRAME_CAPTURE 0x13
#define ADNS3080_SROM_ENABLE 0x14
#define ADNS3080_FRAME_PERIOD_MAX_BOUND_LOWER 0x19
#define ADNS3080_FRAME_PERIOD_MAX_BOUND_UPPER 0x1a
#define ADNS3080_FRAME_PERIOD_MIN_BOUND_LOWER 0x1b
#define ADNS3080_FRAME_PERIOD_MIN_BOUND_UPPER 0x1c
#define ADNS3080_SHUTTER_MAX_BOUND_LOWER 0x1e
#define ADNS3080_SHUTTER_MAX_BOUND_UPPER 0x1e
#define ADNS3080_SROM_ID 0x1f
#define ADNS3080_OBSERVATION 0x3d
#define ADNS3080_INVERSE_PRODUCT_ID 0x3f
#define ADNS3080_PIXEL_BURST 0x40
#define ADNS3080_MOTION_BURST 0x50
#define ADNS3080_SROM_LOAD 0x60
#define ADNS3080_PRODUCT_ID_VAL 0x17
uint8_t SPI_transfer(uint8_t data){
//while ((SPSR & (1<<SPIF)) == 0);
SPDR = data;
//_delay_us(2);
while ((SPSR & (1<<SPIF)) != 0);
while ((SPSR & (1<<SPIF)) == 0);
// _delay_us(40);
return SPDR;
}
void SPI_init() {
SPCR = (1<SPE); //enable
}
//************************************************************************
// function usart_gets()
// purpose: gets characters in first rs232 PORT
// arguments:
// corresponding string where characters are put
// return: corresponding string where characters are put
// note: 19200,8,n,2 hard coded : transmission
// initialisation uart prealable requise
//************************************************************************
void usart_gets(char str[]) {
uint8_t i=0;
do {
str[i]=usart_receive();
i++;
} while(str[i-1]!=0x0D); // carriage return ?
str[i-1]=0;//end of string
}
//************************************************************************
// function usart_puts()
// purpose: puts characters in first rs232 PORT
// arguments:
// corresponding string
// return:
// note: 19200,8,n,2 hard coded : transmission
// initialisation uart prealable requise
//************************************************************************
void usart_puts(char str[]){
uint8_t i=0;
do {
usart_send(str[i]);
i++;
} while(str[i]!=0);
}
//************************************************************************
// function usart_puts_hexa()
// purpose: puts number in hexadecimel in first rs232 PORT
// arguments:
// corresponding number
// return:
// note: 19200,8,n,2 hard coded : transmission
// initialisation uart prealable requise
// only for 16-bit numbers and then Q3.13 numbers
//************************************************************************
void usart_puts_hexa(int nbQ3_13){
int8_t i=0,digit=0;
char char_digit;
usart_send('0');usart_send('X');
for (i=12;i>-1;i-=4) {// only four digits
digit = (nbQ3_13 >> i) & 0x0F;
char_digit=digit+0x30;
if (char_digit>0x39) char_digit += 7;
usart_send(char_digit);
}
}
//************************************************************************
// function usart_init()
// purpose: init first rs232 PORT
// arguments:
// no argument
// return:
// note: 19200,8,n,2 hard coded : transmission and reception
//************************************************************************
void usart_init(void) {
UCSRB = (1<<TXEN)|((1<<RXEN)); // transmission et reception
}
//************************************************************************
// function uart_send()
// purpose: put character in first rs232 PORT
// arguments:
// corresponding character
// return:
// note: 19200,8,n,2 hard coded
// initialisation uart prealable requise
//************************************************************************
void usart_send(unsigned char ch){
while(!(UCSRA & (1<<UDRE)));
UDR = ch;
}
//************************************************************************
// function uart_receive()
// purpose: read character in second rs232 PORT
// arguments:
// corresponding character
// return: blocking sub return char
// note: 19200,8,n,2 hard coded, non-blocking sub return 0 if no data present
// initialisation uart prealable requise
//************************************************************************
char usart_receive(void){
while (!(UCSRA & (1<<RXC))); //attente tant que Data Present en réception
return UDR;
}
//************************************************************************
// function usart_puts_deci()
// purpose: puts number in decimal in first rs232 PORT
// arguments:
// corresponding number
// return:
// note: 19200,8,n,2 hard coded : transmission
// initialisation uart prealable requise
// only for 8-bit positive numbers
//************************************************************************
void usart_puts_deci(uint8_t nb){
usart_send((nb/100) +'0');
usart_send((nb/10)%10 +'0');
usart_send((nb%10) +'0');
}
void mousecam_reset()
{
PORTC |= 0x10; // RST High
_delay_ms(1); // reset pulse >10us
PORTC &= 0xEF; // RST low
_delay_ms(35); // 35ms from reset to functional
}
void mousecam_write_reg(uint8_t reg, uint8_t val)
{
PORTC &= 0xFB; // SSEL=CS low
SPI_transfer(reg | 0x80);
SPI_transfer(val);
PORTC |= 0x04; // SSEL=CS high
_delay_us(50);
}
uint8_t mousecam_read_reg(uint8_t reg)
{
uint8_t ret=0;
PORTC &= 0xFB; // SSEL=CS low
SPI_transfer(reg);
_delay_us(75);
ret = SPI_transfer(0xff);
PORTC |= 0x04; // SSEL=CS high
_delay_us(1);
return ret;
}
int8_t mousecam_init()
{
int8_t pid=0;
PORTC |= 0x04; // SSEL=CS high
mousecam_reset();
pid = mousecam_read_reg(ADNS3080_PRODUCT_ID);
if(pid != ADNS3080_PRODUCT_ID_VAL)
return -1;
// turn on sensitive mode
mousecam_write_reg(ADNS3080_CONFIGURATION_BITS, 0x19);
return 0;
}
// pdata must point to an array of size ADNS3080_PIXELS_X x ADNS3080_PIXELS_Y
// you must call mousecam_reset() after this if you want to go back to normal operation
int8_t mousecam_frame_capture(uint8_t *pdata)
{
mousecam_write_reg(ADNS3080_FRAME_CAPTURE,0x83);
PORTC &= 0xFB; // SSEL=CS low
SPI_transfer(ADNS3080_PIXEL_BURST);
_delay_us(50);
uint8_t pix;
uint8_t started = 0;
int count;
int timeout = 0;
int8_t ret = 0;
for(count = 0; count < ADNS3080_PIXELS_X * ADNS3080_PIXELS_Y; )
{
pix = SPI_transfer(0xff);
_delay_us(10);
if(started==0)
{
if(pix&0x40)
started = 1;
else
{
timeout++;
if(timeout==100)
{
usart_puts("timeout!\n");
ret = -1;
break;
}
}
}
if(started==1)
{
pdata[count++] = (pix & 0x3f)<<2; // scale to normal grayscale byte range
}
}
PORTC |= 0x04; // SSEL=CS high
_delay_us(14);
return ret;
}
struct MD
{
uint8_t motion;
int8_t dx, dy;
uint8_t squal;
int16_t shutter;
uint8_t max_pix;
};
void mousecam_read_motion(struct MD *p)
{
PORTC &= 0xFB; // SSEL=CS low
SPI_transfer(ADNS3080_MOTION_BURST);
_delay_us(75);
p->motion = SPI_transfer(0xff);
p->dx = SPI_transfer(0xff);
p->dy = SPI_transfer(0xff);
p->squal = SPI_transfer(0xff);
p->shutter = SPI_transfer(0xff)<<8;
p->shutter |= SPI_transfer(0xff);
p->max_pix = SPI_transfer(0xff);
PORTC |= 0x04; // SSEL=CS high
_delay_us(5);
}
char asciiart(int k)
{
static char foo[] = "WX86*3I>!;~:,`. ";
return foo[k>>4];
}
Et voici maintenant un programme principal en C :
#ifdef FRAME
uint8_t frame[ADNS3080_PIXELS_X * ADNS3080_PIXELS_Y];
#endif
int main(){
int X=0,Y=0;
// setup()
SPCR = 0;
//SPI.setClockDivider(SPI_CLOCK_DIV32);
//SPI.setDataMode(SPI_MODE3);
SPCR |= ((1<<CPOL) | (1<<CPHA)); // mode 3
SPCR |= (1<<SPR1); // poids faible division = 2
SPSR |= (1 << 1); // poids fort division = 8
//SPI.setBitOrder(MSBFIRST);
usart_init();
if(mousecam_init()==-1)
{
usart_puts("Mouse cam failed to init\n");
while(1);
}
// loop()
while(1) {
#ifdef FRAME
usart_puts("Mouse cam OK\n");
// this section grabs frames and outputs them as ascii art
if(mousecam_frame_capture(frame)==0)
{
int i,j,k;
for(i=0, k=0; i<ADNS3080_PIXELS_Y; i++)
{
for(j=0; j<ADNS3080_PIXELS_X; j++, k++)
{
usart_send(asciiart(frame[k]));
usart_send(' ');
}
usart_send(0x0D);usart_send(0x0A);;
}
}
usart_send(0x0D);usart_send(0x0A);
_delay_ms(250);
#else
// if enabled this section produces a bar graph of the surface quality that can be used to focus the camera
// also drawn is the average pixel value 0-63 and the shutter speed and the motion dx,dy.
//int val = mousecam_read_reg(ADNS3080_PIXEL_SUM);
struct MD md;
mousecam_read_motion(&md);
X += (int)md.dx;
Y += (int)md.dy;
if(X > -1) usart_send('+');
if(X < 0) usart_send('-');
if(abs(X) < 10) usart_send('0');
usart_puts_deci(abs(X));
usart_send(',');
if(Y > -1) usart_send('+');
if(Y < 0) usart_send('-');
if(abs(Y) < 10) usart_send('0');
usart_puts_deci(abs(Y));
usart_send(0x0D);usart_send(0x0A);
_delay_ms(250);
#endif
} // while(1)
return 0;
}
Ce programme fonctionne correctement à condition qu'il soit compilé pour un ATMega32, même s'il finira en définitive dans un ATMega16. Ceci est un avantage des SOCs qui est inutilisable sur un microcontrôleur du commerce. La compilation pour ATMega16 nous a donné un résultat typique de plantage à cause de la saturation de la pile. Nous avons donc essayé une compilation pour ATMega32 et cela a fonctionné immédiatement. Pourquoi ?
- Il y a un tableau de 900 octets dans le programme qui prend à lui tout seul 88% de la RAM de l'ATMega16 qui est de 1ko
- L'ATMega32 contient 2ko de RAM (le double donc de l'ATMega16)
- Notre ATMega16 contient 4ko de RAM, mais le compilateur est incapable de la gérer par défaut.
Module de communication radio nRF2401
[modifier | modifier le wikicode]Les modules radio nRF2401 peuvent être trouvés entre 1 et 2 € pour les versions de base qui ont une antenne gravée sur le circuit. Cette antenne gravée limite bien sûr la portée, mais celle-ci est suffisante pour réaliser par exemple une télécommande de Robot mobile.
Nous allons partir d'un code VHDL sur Internet pour réaliser une communication à distance entre un FPGA et un microcontrôleur.
Essai sur microcontrôleur
[modifier | modifier le wikicode]La carte NRF24L01 s'alimente obligatoirement en 3,3V. Pour ceux qui veulent utiliser des cartes Arduino, c'est naturellement possible à condition que ces cartes possèdent une sortie régulée en 3,3V. Ce n'est pas le cas de toutes les cartes Arduino.
Une fois trouvée cette alimentation en 3,3V, une bonne nouvelle est que les entrées acceptent le 5V de l'Arduino. En clair vous n'aurez aucun problème à utiliser ce module avec les Arduino 8/32 bits.
Essai avec un FPGA
[modifier | modifier le wikicode]Aucun code fonctionnel n'est présenté pour le moment.
Voir aussi
[modifier | modifier le wikicode]- communiquer sans fil avec un module nrf24l01 la bibliotheque mirf et une carte arduino genuino
- nrf24l01 FPGA (VHDL) Lien innactif depuis 2020
- GitHub librairie en c
- GitHub librairie MIRF pour Arduino
- Librairie Mirf en C
- WIKI:NRF24L01-Mirf
Robot auto équilibré (ou auto balancé)
[modifier | modifier le wikicode]Nous avions annoncé dès le départ que le mot shield serait pris au sens large dans ce chapitre. Et bien là, vous ne serez pas déçu. Nous allons en effet terminer ce chapitre par un shield Aruino bien plus complexe que ceux qui précèdent puisqu'il s'agit d'un robot complet. Pourquoi le classer dans ce chapitre ? Tout simplement parceque pour le réaliser, nous sommes parti d'un kit pour Arduino et avons remplacé l'Arduino par un FPGA.
Présentation du kit
[modifier | modifier le wikicode]Le sainsmart balancing robot kit est un kit dont le prix varie fortement. Nous l'avions acheté pour 100 € il y a deux ans et fini par le rendre rendre stable avec une troisième roue (roue castor). Pour tenter notre chance une deuxième fois avec l'auto-équilibre, nous en avons acheté un nouveau à 80 € chez le même fournisseur et pour une version un peu améliorée par rapport à l'ancienne.
Du kit, nous n'utiliserons absolument pas la partie commande architecturée autour d'une carte Arduino UNO puisque nous avons l'intention de la remplacer par une carte FPGA que nous allons présenter maintenant.
FPGA : une carte Mojo pour tout commander
[modifier | modifier le wikicode]La carte FPGA MOJO est suffisamment particulière pour être présentée brièvement. Elle est architecturée autour d'un FPGA Spartan6 XC6SLX9 et d'un microcontrôleur ATMega32U4. Ce microcontrôleur 32U4 est celui qui se trouve dans la carte Arduino Leonardo. Le rôle du microcontrôleur est de :
- charger les programmes (*.bit) dans le FPGA à l'aide d'un outil spécifique sur le PC (MOJO Loader) écrit en Java. La destination peut être soit dans le FPGA directement, soit dans une Flash qui sera alors automatiquement chargée dans le FPGA lors d'une mise sous tension.
- gérer le transfert de données du FPGA vers le 32U4 par une liaison série (TTL 3,3 V) et le transformer en USB, ce qui était en général réalisé par les circuits spécialisés FTDI. Nous parlons au passé car les platines Arduino Leonardo utilisent maintenant le 32U4 pour ce travail.
- gérer le transfert de données des convertisseurs analogiques numériques vers le FPGA en utilisant le protocole SPI.
Mais pour interfacer cette carte qui fonctionne en 3,3 V avec la puissance qui demande 5V pour les signaux de commande ainsi que le capteur, il nous faut réaliser une carte de commande que nous allons présenter maintenant.
Processeur embarqué dans le FPGA et périphériques
[modifier | modifier le wikicode]L'utilisation d'un accéléromètre MPU6050 qui fonctionne en I2C nous impose d'office à choisir une version améliorée de l'ATMega16, celle qui est capable de gérer I2C (présentée dans un autre chapitre de ce livre).
Nous avons d'autre part rencontré beaucoup de problèmes difficilement résolubles à l'aide de nos 8 LEDs. Il nous faut forcément développer une interface série pour pouvoir suivre ce qui se passe entre le capteur et la commande de moteur. Deux possibilités s'offrent à nous :
- utiliser un convertisseur RS232 TTL 5V/3,3 V vers USB (disponible pour quelques Euros)
- utiliser l'ATMega 32U4 présent sur la carte MOJO pour faire ce travail
Nous avons acheté un convertisseur mais pendant les quelques jours d'attente pour sa réception, nous avons réalisé une version de notre ATMega16 qui communique parfaitement avec l'ATMega32U4. C'est ce que nous allons présenter maintenant.
État des lieux pour la liaison série
[modifier | modifier le wikicode]Le processeur embarqué ATMega16 possède depuis le début une liaison série. Celle-ci peut être utilisée sans problème avec le fameux convertisseur dont nous venons de parler. Quelques essais nous ont obligé cependant à renoncer à son utilisation pour communiquer avec l'ATMega32U4. En effet cette communication est un peu particulière ... et pas trop documentée. Mais il existe un programme VHDL qui réalise un écho sur cette liaison et qui est fonctionnel. Partir d'un programme fonctionnel pour l'interfacer à notre processeur embarqué c'est toujours plus simple que de modifier un programme VHDL qui ne fonctionne pas. Nous avons donc décidé de faire une deuxième liaison série pour la transmission de données du processeur embarqué vers le processeur 32U4.
Réalisation d'une deuxième transmission série
[modifier | modifier le wikicode]Comme promis, nous allons partir d'une partir d'une version VHDL qu'il est possible de trouver dans Mojo-Base-VHDL-1.1.zip. Essayez-la et vous verrez que la partie gestion de la liaison série fonctionne parfaitement en écho. Il est naturellement possible d'envisager d'interfacer tout ce code au processeur embarqué mais nous préférerons n'utiliser que le module "serial_tx.vhd". Ce module est suffisamment court pour être donné complètement maintenant :
----------------------------------------------------------------------------------
-- serial_tx VHDL
-- Translated from Mojo-base Verilog project @ http://embeddedmicro.com/frontend/files/userfiles/files/Mojo-Base.zip
-- by Xark
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.NUMERIC_STD.all;
entity serial_tx is
generic (
CLK_PER_BIT : natural := 50;
CTR_SIZE : natural := 6
);
port (
clk : in std_logic;
rst : in std_logic;
tx : out std_logic;
tx_block : in std_logic;
busy : out std_logic;
new_data : in std_logic;
data : in std_logic_vector(7 downto 0)
);
end entity serial_tx;
architecture RTL of serial_tx is
type tx_state_type is (IDLE, START_BIT, DATA_BITS, STOP_BIT);
signal state_d, state_q : tx_state_type := IDLE;
signal ctr_d, ctr_q : unsigned(CTR_SIZE-1 downto 0);
signal bit_ctr_d, bit_ctr_q : unsigned(2 downto 0);
signal data_d, data_q : std_logic_vector(7 downto 0);
signal tx_d, tx_q : std_logic;
signal busy_r : std_logic;
signal block_d, block_q : std_logic;
begin
tx <= tx_q;
busy <= busy_r;
tx_comb: process(tx_block, block_q, ctr_q, bit_ctr_q, data_q, state_q, data, new_data)
begin
block_d <= tx_block;
ctr_d <= ctr_q;
bit_ctr_d <= bit_ctr_q;
data_d <= data_q;
state_d <= state_q;
case state_q is
when IDLE =>
if block_q = '1' then
busy_r <=