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.
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.
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.
- 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.
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
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
- 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]
#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 particulière 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 particulière 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 particulière
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 statut 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 ! À 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) |
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; À 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;
}
- 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 bibliothèque 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 <= '1';
tx_d <= '1';
else
busy_r <= '0';
tx_d <= '1';
bit_ctr_d <= "000";
ctr_d <= (others => '0');
if new_data = '1' then
data_d <= data;
state_d <= START_BIT;
busy_r <= '1';
end if;
end if;
when START_BIT =>
busy_r <= '1';
ctr_d <= ctr_q + 1;
tx_d <= '0';
if ctr_q = (CLK_PER_BIT-1) then
ctr_d <= (others => '0');
state_d <= DATA_BITS;
end if;
when DATA_BITS =>
busy_r <= '1';
tx_d <= data_q(0);
ctr_d <= ctr_q + 1;
if ctr_q = (CLK_PER_BIT-1) then
data_d <= "0" & data_q(7 downto 1);
ctr_d <= (others => '0');
bit_ctr_d <= bit_ctr_q + 1;
if bit_ctr_q = 7 then
state_d <= STOP_BIT;
end if;
end if;
when STOP_BIT =>
busy_r <= '1';
tx_d <= '1';
ctr_d <= ctr_q + 1;
if ctr_q = (CLK_PER_BIT-1) then
state_d <= IDLE;
end if;
when others =>
state_d <= IDLE;
end case;
end process tx_comb;
tx_seq: process(clk, rst)
begin
if rst = '1' then
state_q <= IDLE;
tx_q <= '1';
elsif rising_edge(clk) then
state_q <= state_d;
tx_q <= tx_d;
block_q <= block_d;
data_q <= data_d;
bit_ctr_q <= bit_ctr_d;
ctr_q <= ctr_d;
end if;
end process tx_seq;
end RTL;
Maintenant, il nous faut interfacer ce code au processeur. Comme d'habitude tout se passe dans le fichier "ioi2c.vhd" qui est donné maintenant :
-------------------------------------------------------------------------------
--
-- 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;
tx_block : 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;
tx : out std_logic; --special MOJO
-- added for right and left motor
LeftEnOut, LeftDirOut, RightEnOut, RightDirOut : 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 WDTCR : std_logic_vector(7 downto 0) := X"41";
constant ASSR : std_logic_vector(7 downto 0) := X"42";
constant OCR2 : std_logic_vector(7 downto 0) := X"43";
constant TCNT2 : std_logic_vector(7 downto 0) := X"44";
constant TCCR2 : std_logic_vector(7 downto 0) := X"45";
constant ICR1L : std_logic_vector(7 downto 0) := X"46";
constant ICR1H : std_logic_vector(7 downto 0) := X"47";
constant OCR1BL : std_logic_vector(7 downto 0) := X"48";
constant OCR1BH : std_logic_vector(7 downto 0) := X"49";
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 particulière de TWINT
-- i2c signals
SCL : inout std_logic;
SDA : inout std_logic
);
end component topi2c;
component HB5RefComp is
Port ( ck : in STD_LOGIC; -- system clock (25MHz)
bitDirIn : in STD_LOGIC; -- User direction request
vecDfIn : in STD_LOGIC_VECTOR(7 downto 0); -- Duty factor value
bitEnOut : out STD_LOGIC:= '0'; -- Enable pin for Pmod HB5
bitDirOut : out STD_LOGIC:= '1'); -- Dir pin for Pmod HB5
end component;
signal U_RX_READY : std_logic;
signal U_TX_BUSY : std_logic;
signal U_RX_DATA : std_logic_vector( 7 downto 0);
signal L_INTVEC : std_logic_vector( 5 downto 0);
signal L_LEDS : std_logic;
signal L_RD_UART : std_logic;
signal L_RX_INT_ENABLED : std_logic;
signal L_TX_INT_ENABLED : std_logic;
signal L_WE_UART : std_logic;
--> added 2011/10/19
signal baud_clk : std_logic;
--<
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;
signal s_vecDfIn_Gauche, s_vecDfIn_Droite : std_logic_vector(7 downto 0);
signal s_RightDirIn, s_LeftDirIn : std_logic;
signal tx_busy, new_tx_data, new_tx_data_d : std_logic;
signal tx_data : std_logic_vector(7 downto 0);
-- Pour timer ms
signal prescaler : std_logic_vector( 15 downto 0);
signal ENO_1ms : std_logic;
signal timerms, reg_timerms : std_logic_vector( 31 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 particulière 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';
LeftMotor:HB5RefComp port map (
ck => I_CLK,
bitDirIn => s_LeftDirIn,
vecDfIn => s_vecDfIn_Gauche,
bitEnOut => LeftEnOut,
bitDirOut =>LeftDirOut
);
RightMotor:HB5RefComp port map (
ck => I_CLK,
bitDirIn => s_RightDirIn,
vecDfIn => s_vecDfIn_Droite,
bitEnOut => RightEnOut,
bitDirOut =>RightDirOut
);
serial_tx : entity work.serial_tx
generic map (
CLK_PER_BIT => 50,
CTR_SIZE => 7
)
port map (
clk => I_CLK,
rst => I_CLR,
tx => tx,
tx_block => tx_block,
busy => tx_busy,
data => tx_data,
new_data => new_tx_data_d
);
-- IO read process
--
iord: process(I_ADR_IO, I_PINB,
U_RX_DATA, U_RX_READY, L_RX_INT_ENABLED,
U_TX_BUSY, L_TX_INT_ENABLED,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 particulière
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
--& not avr_rx_busy -- Tx ready
& tx_busy --second asynchronous serial
--& '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 DDRA => --DDRA
Q_Dout(0) <= s_RightDirIn;
Q_DOUT(1) <= s_LeftDirIn;
Q_DOUT(7 downto 2) <= "000000";
when WDTCR => Q_DOUT <= reg_timerms(7 downto 0);
when ASSR => Q_DOUT <= reg_timerms(15 downto 8);
when OCR2 => Q_DOUT <= reg_timerms(23 downto 16);
when TCNT2 => Q_DOUT <= reg_timerms(31 downto 24);
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);
-- PWM moteurs droit et gauche
when PORTA => -- PORTA
s_vecDfIn_Gauche <= I_DIN;
--L_LEDS <= not L_LEDS;
when PINA => -- PORTA
s_vecDfIn_Droite <= I_DIN;
when DDRA => --DDRA
s_RightDirIn <= I_DIN(0);
s_LeftDirIn <= I_DIN(1);
-- 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 TCNT2 => tx_data <= I_DIN;
when X"40" => -- UCSRC/UBRRH: (ignored)
when WDTCR => reg_timerms <= timerms;
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
new_tx_data <= I_WE_IO when (I_ADR_IO = TCNT2) else '0';
process(I_CLK) begin
if rising_edge(I_CLK) then
new_tx_data_d <= new_tx_data;
end if;
end process;
--> removed 2011/10/18
-- Q_LEDS(1) <= L_LEDS;
-- Q_LEDS(0) <= not L_LEDS;
--<
Q_INTVEC <= L_INTVEC;
-- prescaler TIMER ms
process(I_CLK) begin
if rising_edge(I_CLK) then
if prescaler < 49999 then
prescaler <= prescaler +1;
else
prescaler <= (others=> '0');
end if;
end if;
end process;
with prescaler select
ENO_1ms <= '1' when "1100001101001111", --49999
'0' when others;
-- TIMER ms proprement dit
process(I_CLK,ENO_1ms) begin
if rising_edge(I_CLK) then
if ENO_1ms = '1' then
timerms <= timerms +1;
end if;
end if;
end process;
end Behavioral;
Réalisation d'une commande des moteurs
[modifier | modifier le wikicode]La partie puissance de commande des moteurs est réalisée par une carte bon marché architecturée autour du L298. Pour réaliser ce type de commande dans un autre projet, nous avions utilisé les modules PMOD HB5 de chez Digilent ainsi que le code VHDL associé. Ce VHDL n'est pas tout à fait approprié car il n'est pas capable de gérer le mode freinage des moteurs lors d'un changement de direction. Par paresse, nous l'avons quand même utilisé ce qui a abouti à la destruction de deux modules L298N !
Dans ce cas des décisions énergiques s'imposent : nous avons donc décidé d'écrire nos propres modules de commande. Nous avons gardé la bonne idée de l'ancien code : surveiller les changements de directions des moteurs et réaliser un freinage (matériel). Cela permettra d'utiliser cette carte avec des étudiants sans leur demander de gérer ces problèmes par programmation.
Voici donc le code correspondant :
brary IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
--Library UNISIM;
--use UNISIM.vcomponents.all;
entity halfModL298N is
Port ( clk : in STD_LOGIC; -- system clock (25MHz)
bitDirIn : in STD_LOGIC; -- User direction request
pwmIn : in STD_LOGIC_VECTOR(7 downto 0); -- Duty factor value
pwmEnOut : out STD_LOGIC:= '0'; -- EN pin for mod L298N
-- IN(X), IN(X-1) Dir pins for mod L298N
bitDirOut : out STD_LOGIC_VECTOR(1 downto 0) := "00"); -- Dir pins for mod L298N
end entity halfModL298N;
architecture arch_L298N of halfModL298N is
signal cmpt_pwm : std_logic_vector(13 downto 0); -- environ 1526 Hz
signal cnt_delay : std_logic_vector(2 downto 0):="000";
signal stopIsNotFinished, end_pwm, clr : std_logic;
signal bitDir_Last, startDelay,end_cnt_delay : std_logic;
signal state : std_logic_vector(1 downto 0) := "01";
signal entrees : std_logic_vector(1 downto 0);
begin
-- triangle signal realization
process(clk) begin
if rising_edge(clk) then
cmpt_pwm <= cmpt_pwm + 1;
end if;
end process;
-- comparison for pwm
pwmEnOut <= '0' when stopIsNotFinished = '1' else
'1' when cmpt_pwm(13 downto 6)< pwmIn else
'0';
-- detection of end of cmpt_pwm
end_pwm <= '1' when cmpt_pwm = "11111111111111" else
'0';
-- tracking bitDirIn and generating STOP during a delay
process(clk) begin
if rising_edge(clk) then
bitDir_Last <= bitDirIn;
end if;
end process;
startDelay <= bitDir_Last XOR bitDirIn; -- direction change detection
process(clk) begin
if rising_edge(clk) then
if clr = '1' then
cnt_delay <= "000";
elsif stopIsNotFinished = '1' and end_pwm = '1' then
cnt_delay <= cnt_delay + 1;
end if;
end if;
end process;
-- litle automaton with two bits state
end_cnt_delay <= '1' when cnt_delay = "111" else
'0';
process(clk) begin
if rising_edge(clk) then
state(1) <= (state(0) and startDelay) or (state(1) and not end_cnt_delay);
state(0) <= (state(1) and end_cnt_delay) or (state(0) and not startDelay);
end if;
end process;
-- automaton's outputs managment
clr <= state(0);
stopIsNotFinished <= state(1);
-- direction bits managment
-- to be registered ???
-- bitDirOut <= "01";
-- bitDirOut <= "00" when stopIsNotFinished = '1' else
-- "01" when bitDirIn ='1' else
-- "10" when bitDirIn ='0' else
-- "00";
entrees <= stopIsNotFinished & bitDirIn;
with entrees select
bitDirOut <= "00" when "10",
"00" when "11",
"01" when "01",
"10" when others;
end architecture arch_L298N;
Utilisation du MPU6050
[modifier | modifier le wikicode]Le MPU6050 est un accéléromètre gyroscope (6 axes) relativement bon marché : il peut être trouvé entre 1 € et 2 € assez facilement.
Il existe une bibliothèque complète de gestion du MPU6050 pour Arduino. Elle est composée, entre autre, de deux fichiers principaux :
- MPU6050.h
- MPU6050.cpp
qui ont été développés par Jeff Rowberg <jeff@rowberg.net>. Des exemples d'utilisation supplémentaires peuvent aussi être trouvés.
Nous allons nous contenter dans cette section d'une bibliothèque beaucoup plus simple.
Initialisation
[modifier | modifier le wikicode]La communication entre le processeur embarqué et le MPU6050 utilise l'i2c. C'est d'ailleurs une raison pour laquelle on n'utilise pas la librairie de Jeff Rowberg car notre processeur embarqué n'est pas compatible à 100% avec le périphérique i2c de l'ATMega328p.
// initialisation i2c
TWIInit();
_delay_us(500);
TWIStartWrite(MPU<<1);
TWIWrite(0x6B);
TWIWriteStop(0x00);
Il s'agit donc d'envoyer dans le registre d'adresse 0x6B la valeur 0x00. En effet le registre 0x6B s'appelle PWR_MGMT_1 et possède un bit particulier (b6) appelé SLEEP qui est initialisé à 1 à la mise sous tension et que l'on doit passer à 0 pour mettre le MPU6050 en marche en le sortant de son mode de veille.
Reste maintenant à configurer les sensibilités nécessaires à votre cahier des charges.
Accéléromètre
[modifier | modifier le wikicode]Il existe plusieurs sensibilités de mesures de l'accélération. Pour la choisir il faut écrire dans le registre d'adresse 0x1C (appelé ACCEL_CONFIG) les valeurs suivantes dans les bits ACCEL_CONFIG[4:3] appelés AFS_SEL[1:0]
- Table de configuration
AFS_SEL[1:0] Valeur pleine échelle 0 +/- 2g 1 +/- 4g 2 +/- 8g 3 +/- 16g
Dans le cas d'un choix à 0, la mesure sera donnée avec 16384/g. Autrement dit, une mesure d'un g donnera 16384 tandis que deux g donnera 32768. Ceci montre que les données sont sur 16 bits et elles sont signées. Le type C pour les récupérer est donc soit "int" soit "int16_t".
Voici donc le code associé à l'initialisation de l'accéléromètre :
// setting the accelerometer to +/- 2g
TWIStartWrite(MPU<<1);
TWIWrite(0x1C);
TWIWriteStop(0x00);
Gyroscope
[modifier | modifier le wikicode]Il existe aussi plusieurs calibres (sensibilités) de mesures de la vitesse de rotation. Pour le choisir il faut écrire dans le registre d'adresse 0x1B (appelé GYRO_CONFIG) les valeurs suivantes dans les bits GYRO_CONFIG[4:3] appelés FS_SEL[1:0]
- Table de configuration
FS_SEL[1:0] Valeur pleine échelle 0 +/- 250 degrés/s 1 +/- 500 degrés/s 2 +/- 1000 degrés/s 3 +/- 2000 degrés/s
Pour FS_SEL=0, les valeurs retournées sont de 131 par °/s. En clair, la valeur maximale de 250°/s donnera 131*250 = 32750.
Voici donc notre code d'initialisation :
// setting the gyro to full scale +/- 250 °/s
TWIStartWrite(MPU<<1);
TWIWrite(0x1B);
TWIWriteStop(0x00);
Lecture des données
[modifier | modifier le wikicode]Voici les registres qui vont vous donner les valeurs de l'accélération :
- Les données d'accélération
Registre(HEX) Registre(DEC) Données 3B 59 ACCEL_XOUT[15:8] 3C 60 ACCEL_XOUT[7:0] 3D 61 ACCEL_YOUT[15:8] 3E 62 ACCEL_YOUT[7:0] 3F 63 ACCEL_ZOUT[15:8] 40 64 ACCEL_ZOUT[7:0]
Voici les registres qui vont vous donner les valeurs de la température :
- Les valeurs de la température
Registre(HEX) Registre(DEC) Données 41 65 TEMP_OUT[15:8] 42 66 TEMP_OUT[7:0]
Bien sûr il n'est pas nécessaire de lire la température mais les ingénieurs ont décidé que celle-ci serait située juste entre les valeurs de l'accéléromètre et les valeurs du gyroscope. Si vous voulez lire les données en une seule opération i2c, vous vous trouverez donc avec les données de température.
Voici donc où sont les données de la vitesse angulaire :
- Les données du gyroscope
Registre(HEX) Registre(DEC) Données 43 67 GYRO_XOUT[15:8] 44 68 GYRO_XOUT[7:0] 45 69 GYRO_YOUT[15:8] 46 70 GYRO_YOUT[7:0] 47 71 GYRO_ZOUT[15:8] 48 72 GYRO_ZOUT[7:0]
Voici comment il est possible de rassembler toutes ces données dans un tableau au fur et à mesure de la lecture :
// reinitialisation pour lecture
TWIStartWrite(MPU<<1);
TWIWriteStop(0x3B);
_delay_ms(1);
TWIStartWrite((MPU<<1)|1);
for (i=0;i<13;i++)
t[i] = TWIReadACK();
t[13] = TWIReadNACKStop();
Reste donc à transformer ces données en rassemblant 2 cases successives 8 bits pour en faire un nombre 16 bits signé.
Utilisation du DMP du MPU6050
[modifier | modifier le wikicode]Notre aventure avec nos modules L298N de puissance déjà présentée est aussi liée à l'utilisation de l'accéléromètre MPU6050 en tout cas de la manière où il a été utilisé jusqu'ici. Cette façon est la plus simple : elle nécessite un processeur avec une interface i2c et c'est tout ! Le problème est qu'alors aucun filtrage n'est effectué : si le robot tombe d'un côté, le moteur va l'accélérer pour le redresser ce qui provoquera une accélération qui sera lue par l'accéléromètre au point de perturber la mesure et faire croire que l'on tombe de l'autre côté. Il en résulte un tremblement intempestif du robot qui de toute façon ne tiendra pas debout. Pour éviter cela, il nous faudrait faire de la fusion de données avec le gyroscope.
Il existe une autre façon de procéder : utiliser une fusion de données interne au MPU6050 réalisée avec un Digital Movement Processor (DMP). Il s'agit d'un processeur 32 bits spécialisé pour ce type de calcul.
Pourquoi n'avons-nous pas commencé par cela ? La façon d'accéder aux données traitées par le DMP n'est pas très simple. Elle nécessite la bibliothèque spéciale de Jeff Rowberg (déjà citée) et en particulier d'un de ses exemples "MPU6050_DMP6.ino". Il fonctionne avec le fichier "MPU6050_6Axis_MotionApps20.h" : lisez donc ce fichier pour vous convaincre que porter cette librairie demandera un certain travail !
À ce point nous en savons assez pour conclure que l'utilisation du DMP nécessitera un Arduino intermédiaire entre le MPU6050 et le FPGA. La raison profonde à cela est que nous avons découvert que le portage de la librairie sur notre processeur embarqué dans notre FPGA ne pourra pas se faire :
Utilisation d'un STM32 avec le MPU6050
[modifier | modifier le wikicode]
Nous avons renoncé à porter la librairie de Jeff Rowberg pour l'utilisation du MPU6050 en mode DMP (voir section précédente) dans notre SOC ATMega16. Il nous faut donc rajouter un processeur entre l'accéléromètre/gyroscope et le FPGA.
Nous avons décidé de faire fonctionner le MPU6050 avec un STM32. Les raisons ?
- le STM32 Blue Pill peut se trouver à 1,50 € en Chine (en 2018)
- la carte STM32 Blue Pill est de petite taille
- le STM32 fonctionne en 3,3 V et n'amène donc pas de problème de tension avec le FPGA
- le STM32 Blue Pill se programme sous l'environnement Arduino (avec quelques petits ajustements quand même).
- nous avons trouvé une version de la librairie DMP de Jeff Rowberg sur Internet fonctionnant avec les 32 bits (ARM Cortex M3 ici sur github)

Cette architecture finale est plus que discutable techniquement :
- les 32 bits sont plus gourmands en énergie que les 8 bits. Un arduino Mini suffirait donc mais il est vendu plus cher que le STM32
- Le 32 bits que nous utilisons ici est largement suffisant pour être tout seul aux commandes du robot. Le FPGA est donc inutile. Nous le gardons seulement parce que nous sommes dans un livre sur les FPGA. Les décisions des commandes (régulation proportionnelle pour le moment) seront donc toujours réalisées par le FPGA tandis que le capteur de position verticale sera composé d'un MPU6050 et d'un STM32.
Il nous faut maintenant choisir un protocole de communication entre le STM32 et le FPGA.
Nous avons décidé de réaliser deux communications différentes entre le STM32 et le FPGA :
- la liaison série originale : nous employons le mot original pour signifier que l'exemple de départ utilisait cette liaison série pour communiquer avec un PC
- une liaison SPI déjà étudiée dans ce livre. Elle consiste à réaliser un esclave SPI qui sera piloté par un microcontrôleur STM32.
Il nous faut d'abord trouver les broches de la platine Blue Pill qui sont utilisées par défaut pour le SPI lorsque la librairie "Arduino" est utilisée. Pour cela nous avons repris un exemple déjà évoqué dans un autre chapitre. Il s'agissait d'utiliser une carte Nexys3 comme esclave SPI. Maintenant, le processeur sera un STM32. Le code STM32 est effectivement très simple :
#include <SPI.h>
#define SPI1_NSS_PIN PA4 //SPI_1 Chip Select pin is PA4.
// SPI_1 SCK is PA5
// SPI_1 MISO is PA6
// SPI_1 MOSI is PA7
void setup() {
// put your setup code here, to run once:
// Setup SPI 1
SPI.begin(); //Initialize the SPI_1 port.
SPI.setBitOrder(MSBFIRST); // Set the SPI_1 bit order
SPI.setDataMode(SPI_MODE0); //Set the SPI_1 data mode 0
SPI.setClockDivider(SPI_CLOCK_DIV16); // Slow speed (72 / 16 = 4.5 MHz SPI_1 speed)
pinMode(SPI1_NSS_PIN, OUTPUT);
}
void loop() {
byte data;
// put your main code here, to run repeatedly:
digitalWrite(SPI1_NSS_PIN, LOW); // manually take CSN low for SPI_1 transmission
data = SPI.transfer(0x55); //Send the HEX data 0x55 over SPI-1 port
digitalWrite(SPI1_NSS_PIN, HIGH); // manually take CSN high between spi transmissions
delay(500);
digitalWrite(SPI1_NSS_PIN, LOW); // manually take CSN low for SPI_1 transmission
data = SPI.transfer(0xAA); //Send the HEX data 0xAA over SPI-1 port
digitalWrite(SPI1_NSS_PIN, HIGH); // manually take CSN high between spi transmissions
delay(500);
}
Ce code fonctionne parfaitement avec un esclave SPI en VHDL déjà étudié dans ce livre.
La carte d'adaptation
[modifier | modifier le wikicode]Commençons par résumer le fonctionnement que l'on vient de décrire pour bien cibler la carte à réaliser.
Répartition des tâches entre MPU6050, STM32 et FPGA
[modifier | modifier le wikicode]Pour résumer ce que nous avons déjà expliqué :
- un accéléromètre MPU6050 est utilisé avec son processeur de calcul intégré, le DMP
- les données du DMP du MPU6050 sont recueillies par un STM32
- le STM32 récupère les données du DMP et s'intéresse particulièrement au calcul de l'angle par rapport à la verticale
- une fois cet angle obtenu il est systématiquement envoyé par SPI au FPGA qui le transforme en entrée pour le processeur (du FPGA)
- le processeur du FPGA calcule le rapport cyclique qu'il faut envoyer aux deux moteurs
- le FPGA réalise le rapport cyclique des moteurs, adapté au composant de puissance utilisé L298N
- ... et le robot tiendra debout peut-être ...
Présentation de la carte d'extension
[modifier | modifier le wikicode]La carte d'adaptation est destinée à se placer sur la carte FPGA MOJO. C'est donc une sorte de bouclier (shield). Son cahier des charges est le suivant :
- adapter les tensions de commande pour le composant de puissance L298N (monté sur une carte spécifique). La documentation du composant L298N laisse entendre en effet une compatibilité TTL donc commandable par du 3,3 V mais que nous n'avons jamais réussi à réaliser.
- réaliser des modifications sur des paramètres importants du régulateur (ou autre). Nous avons choisi pour cela un bouton codeur incrémental.
- recevoir la carte accéléromètre
- aider au débogage avec un afficheur 4 digits 7 segments.
Nous avons réalisé une première carte mais celle-ci présente trois défauts :
- les 8 LEDs de la carte MOJO sont aussi présentes sur le connecteur d'extension et nous en avons utilisé une par mégarde pour la commande de puissance. Ceci peut sembler anecdotique mais c'est le seul moyen de débogage dont on dispose très facilement !
- l'afficheur 4 digits de 7 segments aurait pu être utile lui aussi pour le débogage mais nous l'avons choisi pour son prix réduit et comme le montre la section qui lui est consacré dans ce chapitre, nous ne maîtrisons pas encore son utilisation.
- pas de possibilité de lire le processeur DMP
Une deuxième carte a donc vu le jour pour répondre à tous les problèmes présentés auparavant. Nous avons retirés les afficheurs 7 segments et le bouton codeur incrémental mais naturellement ajouté le STM32 déjà évoqué.
Ressources
[modifier | modifier le wikicode]Le code présenté dans cette section est disponible sur mon site personnel. Il n'est pas encore capable de gérer le maintien du robot en auto-équilibre mais cela viendra un jour... Pour le moment, seule la correction Proportionnelle est effective avec une consigne d'angle. Or quand l'angle est de 0 (la consigne) le robot n'est pas en équilibre : il a donc tendance à se déplacer pour réaliser la consigne à 0, c'est-à-dire l'auto équilibre !
Écran 2.8" SPI
[modifier | modifier le wikicode]Nous avons acheté un 2.8 TFT SPI 240x320 V1.1 pour environ 6 € (2018) mais pour ce prix, le composant responsable de la détection tactile n'est en général pas soudé sur la carte (et pas livré). Cela tombe bien, nous n'avons pas l'intention de nous intéresser au côté tactile pour le moment. Comme vous l'avez remarqué dans le titre de cette section, il s'agit d'un composant SPI donc avec une vitesse de transfert faible. Vous pouvez trouver des écrans avec un transfert de 16 bits à la fois, mais ils sont un peu plus chers (environ 12 € en 2019).
Comme d'habitude, nous allons commencer par étudier le composant à l'aide d'un microcontrôleur. Nous choisissons un microcontrôleur 3,3 V pour éviter les problèmes de tensions. L'écran que nous utilisons peut être alimenté en 3,3 V et est architecturé autour du composant ILI9341.
Étude avec un microcontrôleur 32 bits
[modifier | modifier le wikicode]Nous allons utiliser la carte TivaC LaunchPad de chez Texas Instrument avec l'environnement compatible Arduino : Energia.
Préparation de l'environnement Energia
[modifier | modifier le wikicode]Vous devez télécharger la librairie de gestion du composant central ILI9341 qui a été adaptée à l'environnement Energia : Librairie ILI9341 pour Energia.
Cette librairie doit être installée correctement.
Câblage avec la carte Tiva
[modifier | modifier le wikicode]Le câblage est lié au matériel et au logiciel. Côté matériel, la carte TivaC LaunchPad est utilisée. Côté logiciel, le programme impose un certain nombre de broches :
Tft.begin(PE_1,PE_2,PE_3,PE_4); // CS,DC,BL,RESET pin
impose par exemple CS, DC, BL et RESET. Le SPI utilisé par défaut dans Energia impose le reste. Voici donc le câblage complet :
2.8 TFT | SDO/MISO | LED | SCK | SDI/MOSI | DC | RESET | CS | GND | VCC |
---|---|---|---|---|---|---|---|---|---|
TivaC LaunchPad | PB6 | PE3 | PB4 | PB7 | PE2 | PE4 | PE1 | GND | 3.3V |
Essai
[modifier | modifier le wikicode]Il vous faut choisir dans les exemples par défaut de la librairie. Remarquez quand même que les cartes bon marché ne sont pas équipée du composant destiné à gérer le côté tactile de l'écran. Pour ce type d'écran (le mien) évitez donc l'utilisation de l'exemple paint.
Tous les problèmes associés à la carte MSP432 sont maintenant résolus : le code ci-dessous est compilable et parfaitement fonctionnel depuis le 28/01/2020.
#include <SPI.h>
//Basic Colors
#define RED 0xf800
#define GREEN 0x07e0
#define BLUE 0x001f
#define BLACK 0x0000
#define YELLOW 0xffe0
#define WHITE 0xffff
//Other Colors
#define CYAN 0x07ff
#define BRIGHT_RED 0xf810
#define GRAY1 0x8410
#define GRAY2 0x4208
//TFT resolution 240*320
#define MIN_X 0
#define MIN_Y 0
#define MAX_X 239
#define MAX_Y 319
#define FONT_SPACE 6
#define FONT_X 8
#define FONT_Y 8
const uint8_t simpleFont[][8] =
{
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x5F,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x07,0x00,0x07,0x00,0x00,0x00},
{0x00,0x14,0x7F,0x14,0x7F,0x14,0x00,0x00},
{0x00,0x24,0x2A,0x7F,0x2A,0x12,0x00,0x00},
{0x00,0x23,0x13,0x08,0x64,0x62,0x00,0x00},
{0x00,0x36,0x49,0x55,0x22,0x50,0x00,0x00},
{0x00,0x00,0x05,0x03,0x00,0x00,0x00,0x00},
{0x00,0x1C,0x22,0x41,0x00,0x00,0x00,0x00},
{0x00,0x41,0x22,0x1C,0x00,0x00,0x00,0x00},
{0x00,0x08,0x2A,0x1C,0x2A,0x08,0x00,0x00},
{0x00,0x08,0x08,0x3E,0x08,0x08,0x00,0x00},
{0x00,0xA0,0x60,0x00,0x00,0x00,0x00,0x00},
{0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x00},
{0x00,0x60,0x60,0x00,0x00,0x00,0x00,0x00},
{0x00,0x20,0x10,0x08,0x04,0x02,0x00,0x00},
{0x00,0x3E,0x51,0x49,0x45,0x3E,0x00,0x00},
{0x00,0x00,0x42,0x7F,0x40,0x00,0x00,0x00},
{0x00,0x62,0x51,0x49,0x49,0x46,0x00,0x00},
{0x00,0x22,0x41,0x49,0x49,0x36,0x00,0x00},
{0x00,0x18,0x14,0x12,0x7F,0x10,0x00,0x00},
{0x00,0x27,0x45,0x45,0x45,0x39,0x00,0x00},
{0x00,0x3C,0x4A,0x49,0x49,0x30,0x00,0x00},
{0x00,0x01,0x71,0x09,0x05,0x03,0x00,0x00},
{0x00,0x36,0x49,0x49,0x49,0x36,0x00,0x00},
{0x00,0x06,0x49,0x49,0x29,0x1E,0x00,0x00},
{0x00,0x00,0x36,0x36,0x00,0x00,0x00,0x00},
{0x00,0x00,0xAC,0x6C,0x00,0x00,0x00,0x00},
{0x00,0x08,0x14,0x22,0x41,0x00,0x00,0x00},
{0x00,0x14,0x14,0x14,0x14,0x14,0x00,0x00},
{0x00,0x41,0x22,0x14,0x08,0x00,0x00,0x00},
{0x00,0x02,0x01,0x51,0x09,0x06,0x00,0x00},
{0x00,0x32,0x49,0x79,0x41,0x3E,0x00,0x00},
{0x00,0x7E,0x09,0x09,0x09,0x7E,0x00,0x00},
{0x00,0x7F,0x49,0x49,0x49,0x36,0x00,0x00},
{0x00,0x3E,0x41,0x41,0x41,0x22,0x00,0x00},
{0x00,0x7F,0x41,0x41,0x22,0x1C,0x00,0x00},
{0x00,0x7F,0x49,0x49,0x49,0x41,0x00,0x00},
{0x00,0x7F,0x09,0x09,0x09,0x01,0x00,0x00},
{0x00,0x3E,0x41,0x41,0x51,0x72,0x00,0x00},
{0x00,0x7F,0x08,0x08,0x08,0x7F,0x00,0x00},
{0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00},
{0x00,0x20,0x40,0x41,0x3F,0x01,0x00,0x00},
{0x00,0x7F,0x08,0x14,0x22,0x41,0x00,0x00},
{0x00,0x7F,0x40,0x40,0x40,0x40,0x00,0x00},
{0x00,0x7F,0x02,0x0C,0x02,0x7F,0x00,0x00},
{0x00,0x7F,0x04,0x08,0x10,0x7F,0x00,0x00},
{0x00,0x3E,0x41,0x41,0x41,0x3E,0x00,0x00},
{0x00,0x7F,0x09,0x09,0x09,0x06,0x00,0x00},
{0x00,0x3E,0x41,0x51,0x21,0x5E,0x00,0x00},
{0x00,0x7F,0x09,0x19,0x29,0x46,0x00,0x00},
{0x00,0x26,0x49,0x49,0x49,0x32,0x00,0x00},
{0x00,0x01,0x01,0x7F,0x01,0x01,0x00,0x00},
{0x00,0x3F,0x40,0x40,0x40,0x3F,0x00,0x00},
{0x00,0x1F,0x20,0x40,0x20,0x1F,0x00,0x00},
{0x00,0x3F,0x40,0x38,0x40,0x3F,0x00,0x00},
{0x00,0x63,0x14,0x08,0x14,0x63,0x00,0x00},
{0x00,0x03,0x04,0x78,0x04,0x03,0x00,0x00},
{0x00,0x61,0x51,0x49,0x45,0x43,0x00,0x00},
{0x00,0x7F,0x41,0x41,0x00,0x00,0x00,0x00},
{0x00,0x02,0x04,0x08,0x10,0x20,0x00,0x00},
{0x00,0x41,0x41,0x7F,0x00,0x00,0x00,0x00},
{0x00,0x04,0x02,0x01,0x02,0x04,0x00,0x00},
{0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00},
{0x00,0x01,0x02,0x04,0x00,0x00,0x00,0x00},
{0x00,0x20,0x54,0x54,0x54,0x78,0x00,0x00},
{0x00,0x7F,0x48,0x44,0x44,0x38,0x00,0x00},
{0x00,0x38,0x44,0x44,0x28,0x00,0x00,0x00},
{0x00,0x38,0x44,0x44,0x48,0x7F,0x00,0x00},
{0x00,0x38,0x54,0x54,0x54,0x18,0x00,0x00},
{0x00,0x08,0x7E,0x09,0x02,0x00,0x00,0x00},
{0x00,0x18,0xA4,0xA4,0xA4,0x7C,0x00,0x00},
{0x00,0x7F,0x08,0x04,0x04,0x78,0x00,0x00},
{0x00,0x00,0x7D,0x00,0x00,0x00,0x00,0x00},
{0x00,0x80,0x84,0x7D,0x00,0x00,0x00,0x00},
{0x00,0x7F,0x10,0x28,0x44,0x00,0x00,0x00},
{0x00,0x41,0x7F,0x40,0x00,0x00,0x00,0x00},
{0x00,0x7C,0x04,0x18,0x04,0x78,0x00,0x00},
{0x00,0x7C,0x08,0x04,0x7C,0x00,0x00,0x00},
{0x00,0x38,0x44,0x44,0x38,0x00,0x00,0x00},
{0x00,0xFC,0x24,0x24,0x18,0x00,0x00,0x00},
{0x00,0x18,0x24,0x24,0xFC,0x00,0x00,0x00},
{0x00,0x00,0x7C,0x08,0x04,0x00,0x00,0x00},
{0x00,0x48,0x54,0x54,0x24,0x00,0x00,0x00},
{0x00,0x04,0x7F,0x44,0x00,0x00,0x00,0x00},
{0x00,0x3C,0x40,0x40,0x7C,0x00,0x00,0x00},
{0x00,0x1C,0x20,0x40,0x20,0x1C,0x00,0x00},
{0x00,0x3C,0x40,0x30,0x40,0x3C,0x00,0x00},
{0x00,0x44,0x28,0x10,0x28,0x44,0x00,0x00},
{0x00,0x1C,0xA0,0xA0,0x7C,0x00,0x00,0x00},
{0x00,0x44,0x64,0x54,0x4C,0x44,0x00,0x00},
{0x00,0x08,0x36,0x41,0x00,0x00,0x00,0x00},
{0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00},
{0x00,0x41,0x36,0x08,0x00,0x00,0x00,0x00},
{0x00,0x02,0x01,0x01,0x02,0x01,0x00,0x00},
{0x00,0x02,0x05,0x05,0x02,0x00,0x00,0x00}
};
void TFT_sendCMD(uint8_t index);
void TFT_WRITE_DATA(uint8_t data);
void TFT_sendData(uint16_t data);
void TFT_WRITE_Package(uint16_t *data, uint8_t howmany);
void TFT_backlight_on(void);
void TFT_backlight_off(void);
uint8_t TFT_Read_Register(uint8_t Addr, uint8_t xParameter);
void TFT_TFTinit (void);
uint8_t TFT_readID(void);
void TFT_setCol(uint16_t StartCol,uint16_t EndCol);
void TFT_setPage(uint16_t StartPage,uint16_t EndPage);
void TFT_fillScreen(uint16_t XL, uint16_t XR, uint16_t YU, uint16_t YD, uint16_t color);
void TFT_fillScreen2(void);
void TFT_setXY(uint16_t poX, uint16_t poY);
void TFT_setPixel(uint16_t poX, uint16_t poY,uint16_t color);
void TFT_drawChar( uint8_t ascii, uint16_t poX, uint16_t poY,uint16_t size, uint16_t fgcolor, uint16_t bgcolor);
void TFT_drawString(char *string,uint16_t poX, uint16_t poY, uint16_t size,uint16_t fgcolor, uint16_t bgcolor);
void TFT_fillRectangle(uint16_t poX, uint16_t poY, uint16_t length, uint16_t width, uint16_t color);
void TFT_drawHorizontalLine( uint16_t poX, uint16_t poY,uint16_t length,uint16_t color);
void TFT_drawLine( uint16_t x0,uint16_t y0,uint16_t x1, uint16_t y1,uint16_t color);
void TFT_drawVerticalLine( uint16_t poX, uint16_t poY, uint16_t length,uint16_t color);
void TFT_drawRectangle(uint16_t poX, uint16_t poY, uint16_t length, uint16_t width,uint16_t color);
void TFT_drawCircle(int poX, int poY, int r,uint16_t color);
void TFT_fillCircle(int poX, int poY, int r,uint16_t color);
void TFT_drawTraingle( int poX1, int poY1, int poX2, int poY2, int poX3, int poY3, uint16_t color);
uint8_t TFT_drawNumber(long long_num,uint16_t poX, uint16_t poY,uint16_t size,uint16_t fgcolor, uint16_t bgcolor);
uint8_t TFT_drawFloat(float floatNumber,uint8_t decimal,uint16_t poX, uint16_t poY,uint16_t size,uint16_t fgcolor, uint16_t bgcolor);
uint8_t SPI_transfer(uint8_t data);
void SPI_init();
void setup() {
// put your setup code here, to run once:
pinMode(31,OUTPUT); // P3.1 = 31 = DC
pinMode(18,OUTPUT); //SSEL = CS = P3.6 = 18
pinMode(2,OUTPUT); //led = = P6.0 = 2
pinMode(17,OUTPUT); // RST = P5.7 = 17
pinMode(RED_LED,OUTPUT);
//SPI.begin();
//Serial.begin(9600);
TFT_TFTinit();
TFT_backlight_on(); // turn on the background light
}
void loop() {
uint8_t cmpt=0;
TFT_drawLine(0,0,239,319,RED); //start: (0, 0) end: (239, 319), color : RED
TFT_drawVerticalLine(60,100,100,GREEN); // Draw a vertical line
// start: (60, 100) length: 100 color: green
TFT_drawHorizontalLine(30,60,150,BLUE); //Draw a horizontal line
//start: (30, 60), high: 150, color: blue
delay(500);
cmpt++;
if (cmpt & 0x01) digitalWrite(RED_LED,HIGH); else digitalWrite(RED_LED,LOW);
//Serial.println("Bonjour");
}
void TFT_sendCMD(uint8_t index)
{
// CD = P3.7 = 31
digitalWrite(31,LOW);
//SSEL = CS = P3.6 = 18
digitalWrite(18,LOW);
SPI.transfer(index);
digitalWrite(18,HIGH);
}
void TFT_WRITE_DATA(uint8_t data)
{
digitalWrite(31,HIGH); // CD = P3.7 = 31
digitalWrite(18,LOW); //SSEL = CS = P3.6 = 18
SPI.transfer(data);
digitalWrite(18,HIGH);
}
void TFT_sendData(uint16_t data)
{
uint8_t data1 = data>>8;
uint8_t data2 = data&0xff;
digitalWrite(31,HIGH); // CD = P3.7 = 31
digitalWrite(18,LOW); //SSEL = CS = P3.6 = 18
SPI.transfer(data1);
SPI.transfer(data2);
digitalWrite(18,HIGH);
}
void TFT_WRITE_Package(uint16_t *data, uint8_t howmany)
{
uint8_t data1 = 0;
uint8_t data2 = 0;
digitalWrite(31,HIGH); // CD = P3.7 = 31
digitalWrite(18,LOW); //SSEL = CS = P3.6 = 18
uint8_t count=0;
for(count=0;count<howmany;count++)
{
data1 = data[count]>>8;
data2 = data[count]&0xff;
SPI.transfer(data1);
SPI.transfer(data2);
}
digitalWrite(18,HIGH);
}
void TFT_backlight_on(void)
{
digitalWrite(2,HIGH); //led = = P6.0 = 2
}
void TFT_backlight_off(void)
{
//LED off
digitalWrite(2,LOW); //led = = P6.0 = 2
}
uint8_t TFT_Read_Register(uint8_t Addr, uint8_t xParameter)
{
uint8_t data=0;
TFT_sendCMD(0xd9); /* ext command */
TFT_WRITE_DATA(0x10+xParameter);
digitalWrite(31,LOW); // CD = P3.7 = 31
digitalWrite(18,LOW); //SSEL = CS = P3.6 = 18
SPI.transfer(Addr);
digitalWrite(31,HIGH); // CD = P3.7 = 31
data = SPI.transfer(0);
digitalWrite(18,HIGH); //SSEL = CS = P3.6 = 18
return data;
}
void TFT_TFTinit(void)
{
digitalWrite(18,HIGH); //SSEL = CS = P3.6 = 18
digitalWrite(18,LOW); //SSEL = CS = P3.6 = 18
// CD low
digitalWrite(31,LOW); // CD = P3.7 = 31
//LED off
digitalWrite(2,LOW); //led = = P6.0 = 2
// RST low
digitalWrite(17,LOW); // RST = P5.7 = 17
SPI.begin();
SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(MSBFIRST);
SPI.setClockDivider(SPI_CLOCK_DIV16);
// Strawman transfer, fixes USCI issue on G2553
SPI.transfer(0);
// SSEL=CS high ????
digitalWrite(18,HIGH); //SSEL = CS = P3.6 = 18
// CD high
digitalWrite(31,HIGH); // CD = P3.7 = 31
// PORTC |= 3; // DEBUG ou cela bloque-t-il ?
uint8_t i=0, TFTDriver=0;
// RST low
digitalWrite(17,LOW); // RST = P5.7 = 17
delay(10);
// RST high
digitalWrite(17,HIGH); // RST = P5.7 = 17
for(i=0;i<3;i++)
{
TFTDriver = TFT_readID();
}
TFT_sendCMD(0xCB);
TFT_WRITE_DATA(0x39);
TFT_WRITE_DATA(0x2C);
TFT_WRITE_DATA(0x00);
TFT_WRITE_DATA(0x34);
TFT_WRITE_DATA(0x02);
TFT_sendCMD(0xCF);
TFT_WRITE_DATA(0x00);
TFT_WRITE_DATA(0XC1);
TFT_WRITE_DATA(0X30);
TFT_sendCMD(0xE8);
TFT_WRITE_DATA(0x85);
TFT_WRITE_DATA(0x00);
TFT_WRITE_DATA(0x78);
TFT_sendCMD(0xEA);
TFT_WRITE_DATA(0x00);
TFT_WRITE_DATA(0x00);
TFT_sendCMD(0xED);
TFT_WRITE_DATA(0x64);
TFT_WRITE_DATA(0x03);
TFT_WRITE_DATA(0X12);
TFT_WRITE_DATA(0X81);
TFT_sendCMD(0xF7);
TFT_WRITE_DATA(0x20);
TFT_sendCMD(0xC0); //Power control
TFT_WRITE_DATA(0x23); //VRH[5:0]
TFT_sendCMD(0xC1); //Power control
TFT_WRITE_DATA(0x10); //SAP[2:0];BT[3:0]
TFT_sendCMD(0xC5); //VCM control
TFT_WRITE_DATA(0x3e); //Contrast
TFT_WRITE_DATA(0x28);
TFT_sendCMD(0xC7); //VCM control2
TFT_WRITE_DATA(0x86); //--
TFT_sendCMD(0x36); // Memory Access Control
TFT_WRITE_DATA(0x48); //C8 //48 68绔栧睆//28 E8 妯睆
TFT_sendCMD(0x3A);
TFT_WRITE_DATA(0x55);
TFT_sendCMD(0xB1);
TFT_WRITE_DATA(0x00);
TFT_WRITE_DATA(0x18);
TFT_sendCMD(0xB6); // Display Function Control
TFT_WRITE_DATA(0x08);
TFT_WRITE_DATA(0x82);
TFT_WRITE_DATA(0x27);
TFT_sendCMD(0xF2); // 3Gamma Function Disable
TFT_WRITE_DATA(0x00);
TFT_sendCMD(0x26); //Gamma curve selected
TFT_WRITE_DATA(0x01);
TFT_sendCMD(0xE0); //Set Gamma
TFT_WRITE_DATA(0x0F);
TFT_WRITE_DATA(0x31);
TFT_WRITE_DATA(0x2B);
TFT_WRITE_DATA(0x0C);
TFT_WRITE_DATA(0x0E);
TFT_WRITE_DATA(0x08);
TFT_WRITE_DATA(0x4E);
TFT_WRITE_DATA(0xF1);
TFT_WRITE_DATA(0x37);
TFT_WRITE_DATA(0x07);
TFT_WRITE_DATA(0x10);
TFT_WRITE_DATA(0x03);
TFT_WRITE_DATA(0x0E);
TFT_WRITE_DATA(0x09);
TFT_WRITE_DATA(0x00);
TFT_sendCMD(0XE1); //Set Gamma
TFT_WRITE_DATA(0x00);
TFT_WRITE_DATA(0x0E);
TFT_WRITE_DATA(0x14);
TFT_WRITE_DATA(0x03);
TFT_WRITE_DATA(0x11);
TFT_WRITE_DATA(0x07);
TFT_WRITE_DATA(0x31);
TFT_WRITE_DATA(0xC1);
TFT_WRITE_DATA(0x48);
TFT_WRITE_DATA(0x08);
TFT_WRITE_DATA(0x0F);
TFT_WRITE_DATA(0x0C);
TFT_WRITE_DATA(0x31);
TFT_WRITE_DATA(0x36);
TFT_WRITE_DATA(0x0F);
TFT_sendCMD(0x11); //Exit Sleep
delay(120);
TFT_sendCMD(0x29); //Display on
TFT_sendCMD(0x2c);
TFT_fillScreen2();
}
uint8_t TFT_readID(void)
{
uint8_t i=0;
uint8_t data[3] ;
uint8_t ID[3] = {0x00, 0x93, 0x41};
uint8_t ToF=1;
for(i=0;i<3;i++)
{
data[i]=TFT_Read_Register(0xd3,i+1);
if(data[i] != ID[i])
{
ToF=0;
}
}
return ToF;
}
void TFT_setCol(uint16_t StartCol,uint16_t EndCol)
{
TFT_sendCMD(0x2A); /* Column Command address */
TFT_sendData(StartCol);
TFT_sendData(EndCol);
}
void TFT_setPage(uint16_t StartPage,uint16_t EndPage)
{
TFT_sendCMD(0x2B); /* Column Command address */
TFT_sendData(StartPage);
TFT_sendData(EndPage);
}
void TFT_fillScreen(uint16_t XL, uint16_t XR, uint16_t YU, uint16_t YD, uint16_t color)
{
unsigned long XY=0;
unsigned long i=0;
if(XL > XR)
{
XL = XL^XR;
XR = XL^XR;
XL = XL^XR;
}
if(YU > YD)
{
YU = YU^YD;
YD = YU^YD;
YU = YU^YD;
}
//*********** a redefinir la fonction constrain !!!!!!!!!!!!!!!!!!
//XL = constrain(XL, MIN_X,MAX_X);
//XR = constrain(XR, MIN_X,MAX_X);
//YU = constrain(YU, MIN_Y,MAX_Y);
//YD = constrain(YD, MIN_Y,MAX_Y);
XY = (XR-XL+1);
XY = XY*(YD-YU+1);
TFT_setCol(XL,XR);
TFT_setPage(YU, YD);
TFT_sendCMD(0x2c); /* start to write to display ra */
/* m */
// CD high SSEL=CS low
digitalWrite(31,HIGH); // CD = P3.7 = 31
digitalWrite(18,LOW); //SSEL = CS = P3.6 = 18
uint8_t Hcolor = color>>8;
uint8_t Lcolor = color&0xff;
for(i=0; i < XY; i++)
{
SPI.transfer(Hcolor);
SPI.transfer(Lcolor);
}
// SSEL=CS high
digitalWrite(18,HIGH); //SSEL = CS = P3.6 = 18
}
void TFT_fillScreen2(void)
{
uint16_t i;
TFT_setCol(0, 239);
TFT_setPage(0, 319);
TFT_sendCMD(0x2c); /* start to write to display ra */
/* m */
// CD high SSEL=CS low
digitalWrite(31,HIGH); // CD = P3.7 = 31
digitalWrite(18,LOW); //SSEL = CS = P3.6 = 18
for(i=0; i<38400; i++)
{
SPI.transfer(0);
SPI.transfer(0);
SPI.transfer(0);
SPI.transfer(0);
}
// SSEL=CS high
digitalWrite(18,HIGH); //SSEL = CS = P3.6 = 18
}
void TFT_setXY(uint16_t poX, uint16_t poY)
{
TFT_setCol(poX, poX);
TFT_setPage(poY, poY);
TFT_sendCMD(0x2c);
}
void TFT_setPixel(uint16_t poX, uint16_t poY,uint16_t color)
{
TFT_setXY(poX, poY);
TFT_sendData(color);
}
void TFT_drawChar( uint8_t ascii, uint16_t poX, uint16_t poY,uint16_t size, uint16_t fgcolor, uint16_t bgcolor)
{
//fillRectangle(poX, poY, poX+FONT_X*size, poY+FONT_Y*size, BLACK);
int i;
uint8_t f;
if((ascii>=32)&&(ascii<=127))
{
;
}
else
{
ascii = '?'-32;
}
for (i =0; i<FONT_X; i++ ) {
uint8_t temp = simpleFont[ascii-0x20][i];
for(f=0;f<8;f++)
{
if((temp>>f)&0x01)
{
TFT_fillRectangle(poX+i*size, poY+f*size, size, size, fgcolor);
}
else
TFT_fillRectangle(poX+i*size, poY+f*size, size, size, bgcolor);
}
}
}
void TFT_drawString(char *string,uint16_t poX, uint16_t poY, uint16_t size,uint16_t fgcolor, uint16_t bgcolor)
{
while(*string)
{
TFT_drawChar(*string, poX, poY, size, fgcolor, bgcolor);
*string++;
if(poX < MAX_X)
{
poX += FONT_SPACE*size; /* Move cursor right */
}
}
}
//fillRectangle(poX+i*size, poY+f*size, size, size, fgcolor);
void TFT_fillRectangle(uint16_t poX, uint16_t poY, uint16_t length, uint16_t width, uint16_t color)
{
TFT_fillScreen(poX, poX+length, poY, poY+width, color);
}
void TFT_drawHorizontalLine( uint16_t poX, uint16_t poY,
uint16_t length,uint16_t color)
{
int i;
TFT_setCol(poX,poX + length);
TFT_setPage(poY,poY);
TFT_sendCMD(0x2c);
for(i=0; i<length; i++)
TFT_sendData(color);
}
int abs(int data) {
if (data > 0) return data; else return -data;
}
void TFT_drawLine( uint16_t x0,uint16_t y0,uint16_t x1, uint16_t y1,uint16_t color)
{
int x = x1-x0;
int y = y1-y0;
int dx = abs(x), sx = x0<x1 ? 1 : -1;
int dy = -abs(y), sy = y0<y1 ? 1 : -1;
int err = dx+dy, e2; /* error value e_xy */
for (;;){ /* loop */
TFT_setPixel(x0,y0,color);
e2 = 2*err;
if (e2 >= dy) { /* e_xy+e_x > 0 */
if (x0 == x1) break;
err += dy; x0 += sx;
}
if (e2 <= dx) { /* e_xy+e_y < 0 */
if (y0 == y1) break;
err += dx; y0 += sy;
}
}
}
void TFT_drawVerticalLine( uint16_t poX, uint16_t poY, uint16_t length,uint16_t color)
{
int i;
TFT_setCol(poX,poX);
TFT_setPage(poY,poY+length);
TFT_sendCMD(0x2c);
for(i=0; i<length; i++)
TFT_sendData(color);
}
void TFT_drawRectangle(uint16_t poX, uint16_t poY, uint16_t length, uint16_t width,uint16_t color)
{
TFT_drawHorizontalLine(poX, poY, length, color);
TFT_drawHorizontalLine(poX, poY+width, length, color);
TFT_drawVerticalLine(poX, poY, width,color);
TFT_drawVerticalLine(poX + length, poY, width,color);
}
void TFT_drawCircle(int poX, int poY, int r,uint16_t color)
{
int x = -r, y = 0, err = 2-2*r, e2;
do {
TFT_setPixel(poX-x, poY+y,color);
TFT_setPixel(poX+x, poY+y,color);
TFT_setPixel(poX+x, poY-y,color);
TFT_setPixel(poX-x, poY-y,color);
e2 = err;
if (e2 <= y) {
err += ++y*2+1;
if (-x == y && e2 <= x) e2 = 0;
}
if (e2 > x) err += ++x*2+1;
} while (x <= 0);
}
void TFT_fillCircle(int poX, int poY, int r,uint16_t color)
{
int x = -r, y = 0, err = 2-2*r, e2;
do {
TFT_drawVerticalLine(poX-x, poY-y, 2*y, color);
TFT_drawVerticalLine(poX+x, poY-y, 2*y, color);
e2 = err;
if (e2 <= y) {
err += ++y*2+1;
if (-x == y && e2 <= x) e2 = 0;
}
if (e2 > x) err += ++x*2+1;
} while (x <= 0);
}
void TFT_drawTraingle( int poX1, int poY1, int poX2, int poY2, int poX3, int poY3, uint16_t color)
{
TFT_drawLine(poX1, poY1, poX2, poY2,color);
TFT_drawLine(poX1, poY1, poX3, poY3,color);
TFT_drawLine(poX2, poY2, poX3, poY3,color);
}
uint8_t TFT_drawNumber(long long_num,uint16_t poX, uint16_t poY,uint16_t size,uint16_t fgcolor, uint16_t bgcolor)
{
uint8_t char_buffer[10] = "";
uint8_t i = 0;
uint8_t f = 0;
if (long_num < 0)
{
f=1;
TFT_drawChar('-',poX, poY, size, fgcolor, bgcolor);
long_num = -long_num;
if(poX < MAX_X)
{
poX += FONT_SPACE*size; /* Move cursor right */
}
}
else if (long_num == 0)
{
f=1;
TFT_drawChar('0',poX, poY, size, fgcolor, bgcolor);
return f;
if(poX < MAX_X)
{
poX += FONT_SPACE*size; /* Move cursor right */
}
}
while (long_num > 0)
{
char_buffer[i++] = long_num % 10;
long_num /= 10;
}
f = f+i;
for(; i > 0; i--)
{
TFT_drawChar('0'+ char_buffer[i - 1],poX, poY, size, fgcolor, bgcolor);
if(poX < MAX_X)
{
poX+=FONT_SPACE*size; /* Move cursor right */
}
}
return f;
}
uint8_t TFT_drawFloat(float floatNumber,uint8_t decimal,uint16_t poX, uint16_t poY,uint16_t size,uint16_t fgcolor, uint16_t bgcolor)
{
uint16_t temp=0;
float decy=0.0;
float rounding = 0.5;
uint8_t f=0;
uint8_t i;
if(floatNumber<0.0)
{
TFT_drawChar('-',poX, poY, size, fgcolor, bgcolor);
floatNumber = -floatNumber;
if(poX < MAX_X)
{
poX+=FONT_SPACE*size; /* Move cursor right */
}
f =1;
}
for (i=0; i<decimal; ++i)
{
rounding /= 10.0;
}
floatNumber += rounding;
temp = (uint16_t)floatNumber;
uint8_t howlong=TFT_drawNumber(temp,poX, poY, size, fgcolor, bgcolor);
f += howlong;
if((poX+8*size*howlong) < MAX_X)
{
poX+=FONT_SPACE*size*howlong; /* Move cursor right */
}
if(decimal>0)
{
TFT_drawChar('.',poX, poY, size, fgcolor, bgcolor);
if(poX < MAX_X)
{
poX+=FONT_SPACE*size; /* Move cursor right */
}
f +=1;
}
decy = floatNumber-temp; /* decimal part, 4 */
for(i=0;i<decimal;i++)
{
decy *=10; /* for the next decimal */
temp = decy; /* get the decimal */
TFT_drawNumber(temp,poX, poY, size, fgcolor, bgcolor);
floatNumber = -floatNumber;
if(poX < MAX_X)
{
poX+=FONT_SPACE*size; /* Move cursor right */
}
decy -= temp;
}
f +=decimal;
return f;
}
Étude avec un FPGA
[modifier | modifier le wikicode]Il n'est évidemment pas très difficile de réaliser un périphérique SPI en VHDL. Si l'on n'est pas courageux, Internet nous fournit un certain nombre de cœurs SPI. Ces cœurs ne sont pas orientés processeur mais plutôt destinés à un fonctionnement quasi autonome. Par exemple, ils gèrent :
- un adressage des périphériques SPI
- un slave select automatique (SSEL)
- une taille des données souvent fixée par avance en relation avec SSEL (et cela est un problème pour nous).
- la division pour la construction de l'horloge SPI
- les polarités de l'horloge
Quand on est un peu habitué à une programmation SPI sur microcontrôleur, on sait que le SSEL est géré par un (ou plusieurs) bit(s) de PORT et ne nécessite en aucun cas un adressage automatique ni un SSEL automatique.
Nous voulons garder la gestion classique du SPI dans les microcontrôleurs car cela nous permettra de porter assez facilement des librairies toutes faites pour l'écran. En clair, à priori nous mettons à la poubelle le SSEL automatique. Mais pas si vite, n'avons-nous pas besoin d'un drapeau pour dire que la transmission est terminée ? Oui bien sûr, pour cela le signal SSEL est idéal. Nous allons donc garder ce signal mais ce n'est pas lui qui sortira physiquement du processeur.
Ressource SPI
[modifier | modifier le wikicode]Comme à notre habitude nous allons utiliser un cœur SPI disponible chez OpenCore.org. Seule la partie spi_master.vhd nous intéresse.
Toutes les gestions présentées précédemment semblent nous convenir sauf que si l'on regarde de près on y voit que :
- la gestion de la division est réalisée par un générique
- la gestion des polarités est faite par un générique
Tout ceci est parfait pour une gestion connue au moment de la compilation matérielle mais en aucun cas pour une utilisation pouvant être générale et gérée par des registres de processeur. Il nous a donc fallu faire passer ces génériques en PORT, d'où la présence de ce fichier modifié :
-----------------------------------------------------------------------------------------------------------------------
-- Author: Jonny Doin, jdoin@opencores.org, jonnydoin@gmail.com
--
-- Create Date: 12:18:12 04/25/2011
-- Module Name: SPI_MASTER - RTL
-- Project Name: SPI MASTER / SLAVE INTERFACE
-- Target Devices: Spartan-6
-- Tool versions: ISE 13.1
-- Description:
--
-- This block is the SPI master interface, implemented in one single entity.
-- All internal core operations are synchronous to the 'sclk_i', and a spi base clock is generated by dividing sclk_i downto
-- a frequency that is 2x the spi SCK line frequency. The divider value is passed as a generic parameter during instantiation.
-- All parallel i/o interface operations are synchronous to the 'pclk_i' high speed clock, that can be asynchronous to the serial
-- 'sclk_i' clock.
-- For optimized use of longlines, connect 'sclk_i' and 'pclk_i' to the same global clock line.
-- Fully pipelined cross-clock circuitry guarantees that no setup artifacts occur on the buffers that are accessed by the two
-- clock domains.
-- The block is very simple to use, and has parallel inputs and outputs that behave like a synchronous memory i/o.
-- It is parameterizable via generics for the data width ('N'), SPI mode (CPHA and CPOL), lookahead prefetch signaling
-- ('PREFETCH'), and spi base clock division from sclk_i ('SPI_2X_CLK_DIV').
--
-- SPI CLOCK GENERATION
-- ====================
--
-- The clock generation for the SPI SCK is derived from the high-speed 'sclk_i' clock. The core divides this reference
-- clock to form the SPI base clock, by the 'SPI_2X_CLK_DIV' generic parameter. The user must set the divider value for the
-- SPI_2X clock, which is 2x the desired SCK frequency.
-- All registers in the core are clocked by the high-speed clocks, and clock enables are used to run the FSM and other logic
-- at lower rates. This architecture preserves FPGA clock resources like global clock buffers, and avoids path delays caused
-- by combinatorial clock dividers outputs.
-- The core has async clock domain circuitry to handle asynchronous clocks for the SPI and parallel interfaces.
--
-- PARALLEL WRITE INTERFACE
-- ========================
-- The parallel interface has an input port 'di_i' and an output port 'do_o'.
-- Parallel load is controlled using 3 signals: 'di_i', 'di_req_o' and 'wren_i'. 'di_req_o' is a look ahead data request line,
-- that is set 'PREFETCH' clock cycles in advance to synchronize a pipelined memory or fifo to present the
-- next input data at 'di_i' in time to have continuous clock at the spi bus, to allow back-to-back continuous load.
-- For a pipelined sync RAM, a PREFETCH of 2 cycles allows an address generator to present the new adress to the RAM in one
-- cycle, and the RAM to respond in one more cycle, in time for 'di_i' to be latched by the shifter.
-- If the user sequencer needs a different value for PREFETCH, the generic can be altered at instantiation time.
-- The 'wren_i' write enable strobe must be valid at least one setup time before the rising edge of the last SPI clock cycle,
-- if continuous transmission is intended. If 'wren_i' is not valid 2 SPI clock cycles after the last transmitted bit, the interface
-- enters idle state and deasserts SSEL.
-- When the interface is idle, 'wren_i' write strobe loads the data and starts transmission. 'di_req_o' will strobe when entering
-- idle state, if a previously loaded data has already been transferred.
--
-- PARALLEL WRITE SEQUENCE
-- =======================
-- __ __ __ __ __ __ __
-- pclk_i __/ \__/ \__/ \__/ \__/ \__/ \__/ \... -- parallel interface clock
-- ___________
-- di_req_o ________/ \_____________________... -- 'di_req_o' asserted on rising edge of 'pclk_i'
-- ______________ ___________________________...
-- di_i __old_data____X______new_data_____________... -- user circuit loads data on 'di_i' at next 'pclk_i' rising edge
-- _______
-- wren_i __________________________/ \_______... -- user strobes 'wren_i' for one cycle of 'pclk_i'
--
--
-- PARALLEL READ INTERFACE
-- =======================
-- An internal buffer is used to copy the internal shift register data to drive the 'do_o' port. When a complete word is received,
-- the core shift register is transferred to the buffer, at the rising edge of the spi clock, 'spi_clk'.
-- The signal 'do_valid_o' is set one 'spi_clk' clock after, to directly drive a synchronous memory or fifo write enable.
-- 'do_valid_o' is synchronous to the parallel interface clock, and changes only on rising edges of 'pclk_i'.
-- When the interface is idle, data at the 'do_o' port holds the last word received.
--
-- PARALLEL READ SEQUENCE
-- ======================
-- ______ ______ ______ ______
-- spi_clk bit1 \______/ bitN \______/bitN-1\______/bitN-2\__... -- internal spi 2x base clock
-- _ __ __ __ __ __ __ __ __
-- pclk_i \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \_... -- parallel interface clock (may be async to sclk_i)
-- _____________ _____________________________________... -- 1) rx data is transferred to 'do_buffer_reg'
-- do_o ___old_data__X__________new_data___________________... -- after last rx bit, at rising 'spi_clk'.
-- ____________
-- do_valid_o ____________________________/ \_________... -- 2) 'do_valid_o' strobed for 2 'pclk_i' cycles
-- -- on the 3rd 'pclk_i' rising edge.
--
--
-- The propagation delay of spi_sck_o and spi_mosi_o, referred to the internal clock, is balanced by similar path delays,
-- but the sampling delay of spi_miso_i imposes a setup time referred to the sck signal that limits the high frequency
-- of the interface, for full duplex operation.
--
-- This design was originally targeted to a Spartan-6 platform, synthesized with XST and normal constraints.
-- The VHDL dialect used is VHDL'93, accepted largely by all synthesis tools.
--
------------------------------ COPYRIGHT NOTICE -----------------------------------------------------------------------
--
-- This file is part of the SPI MASTER/SLAVE INTERFACE project http://opencores.org/project,spi_master_slave
--
-- Author(s): Jonny Doin, jdoin@opencores.org, jonnydoin@gmail.com
--
-- Copyright (C) 2011 Jonny Doin
-- -----------------------------
--
-- This source file may be used and distributed without restriction provided that this copyright statement is not
-- removed from the file and that any derivative work contains the original copyright notice and the associated
-- disclaimer.
--
-- This source file is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
-- General Public License as published by the Free Software Foundation; either version 2.1 of the License, or
-- (at your option) any later version.
--
-- This source 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 Lesser General Public License for more
-- details.
--
-- You should have received a copy of the GNU Lesser General Public License along with this source; if not, download
-- it from http://www.gnu.org/licenses/lgpl.txt
--
------------------------------ REVISION HISTORY -----------------------------------------------------------------------
--
-- 2011/04/28 v0.01.0010 [JD] shifter implemented as a sequential process. timing problems and async issues in synthesis.
-- 2011/05/01 v0.01.0030 [JD] changed original shifter design to a fully pipelined RTL fsmd. solved all synthesis issues.
-- 2011/05/05 v0.01.0034 [JD] added an internal buffer register for rx_data, to allow greater liberty in data load/store.
-- 2011/05/08 v0.10.0038 [JD] increased one state to have SSEL start one cycle before SCK. Implemented full CPOL/CPHA
-- logic, based on generics, and do_valid_o signal.
-- 2011/05/13 v0.20.0045 [JD] streamlined signal names, added PREFETCH parameter, added assertions.
-- 2011/05/17 v0.80.0049 [JD] added explicit clock synchronization circuitry across clock boundaries.
-- 2011/05/18 v0.95.0050 [JD] clock generation circuitry, with generators for all-rising-edge clock core.
-- 2011/06/05 v0.96.0053 [JD] changed async clear to sync resets.
-- 2011/06/07 v0.97.0065 [JD] added cross-clock buffers, fixed fsm async glitches.
-- 2011/06/09 v0.97.0068 [JD] reduced control sets (resets, CE, presets) to the absolute minimum to operate, to reduce
-- synthesis LUT overhead in Spartan-6 architecture.
-- 2011/06/11 v0.97.0075 [JD] redesigned all parallel data interfacing ports, and implemented cross-clock strobe logic.
-- 2011/06/12 v0.97.0079 [JD] streamlined wr_ack for all cases and eliminated unnecessary register resets.
-- 2011/06/14 v0.97.0083 [JD] (bug CPHA effect) : redesigned SCK output circuit.
-- (minor bug) : removed fsm registers from (not rst_i) chip enable.
-- 2011/06/15 v0.97.0086 [JD] removed master MISO input register, to relax MISO data setup time (to get higher speed).
-- 2011/07/09 v1.00.0095 [JD] changed all clocking scheme to use a single high-speed clock with clock enables to control lower
-- frequency sequential circuits, to preserve clocking resources and avoid path delay glitches.
-- 2011/07/10 v1.00.0098 [JD] implemented SCK clock divider circuit to generate spi clock directly from system clock.
-- 2011/07/10 v1.10.0075 [JD] verified spi_master_slave in silicon at 50MHz, 25MHz, 16.666MHz, 12.5MHz, 10MHz, 8.333MHz,
-- 7.1428MHz, 6.25MHz, 1MHz and 500kHz. The core proved very robust at all tested frequencies.
-- 2011/07/16 v1.11.0080 [JD] verified both spi_master and spi_slave in loopback at 50MHz SPI clock.
-- 2011/07/17 v1.11.0080 [JD] BUG: CPOL='1', CPHA='1' @50MHz causes MOSI to be shifted one bit earlier.
-- BUG: CPOL='0', CPHA='1' causes SCK to have one extra pulse with one sclk_i width at the end.
-- 2011/07/18 v1.12.0105 [JD] CHG: spi sck output register changed to remove glitch at last clock when CPHA='1'.
-- for CPHA='1', max spi clock is 25MHz. for CPHA= '0', max spi clock is >50MHz.
-- 2011/07/24 v1.13.0125 [JD] FIX: 'sck_ena_ce' is on half-cycle advanced to 'fsm_ce', elliminating CPHA='1' glitches.
-- Core verified for all CPOL, CPHA at up to 50MHz, simulates to over 100MHz.
-- 2011/07/29 v1.14.0130 [JD] Removed global signal setting at the FSM, implementing exhaustive explicit signal attributions
-- for each state, to avoid reported inference problems in some synthesis engines.
-- Streamlined port names and indentation blocks.
-- 2011/08/01 v1.15.0135 [JD] Fixed latch inference for spi_mosi_o driver at the fsm.
-- The master and slave cores were verified in FPGA with continuous transmission, for all SPI modes.
-- 2011/08/04 v1.15.0136 [JD] Fixed assertions (PREFETCH >= 1) and minor comment bugs.
--
-----------------------------------------------------------------------------------------------------------------------
-- TODO
-- ====
--
-----------------------------------------------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.std_logic_unsigned.all;
--================================================================================================================
-- SYNTHESIS CONSIDERATIONS
-- ========================
-- There are several output ports that are used to simulate and verify the core operation.
-- Do not map any signals to the unused ports, and the synthesis tool will remove the related interfacing
-- circuitry.
-- The same is valid for the transmit and receive ports. If the receive ports are not mapped, the
-- synthesis tool will remove the receive logic from the generated circuitry.
-- Alternatively, you can remove these ports and related circuitry once the core is verified and
-- integrated to your circuit.
--================================================================================================================
entity spi_master is
Generic (
N : positive := 32; -- 32bit serial word length is default
PREFETCH : positive := 2); -- prefetch lookahead cycles
-- SPI_2X_CLK_DIV : positive := 5); -- for a 100MHz sclk_i, yields a 10MHz SCK
Port (
sclk_i : in std_logic := 'X'; -- high-speed serial interface system clock
pclk_i : in std_logic := 'X'; -- high-speed parallel interface system clock
rst_i : in std_logic := 'X'; -- reset core
CPOL : std_logic := '0'; -- SPI mode selection (mode 0 default)
CPHA : std_logic := '0'; -- CPOL = clock polarity, CPHA = clock phase.
SPI_2X_CLK_DIV : std_logic_vector(3 downto 0):="0000"; -- for a 100MHz sclk_i, yields a 10MHz SCK
---- serial interface ----
spi_ssel_o : out std_logic; -- spi bus slave select line
spi_sck_o : out std_logic; -- spi bus sck
spi_mosi_o : out std_logic; -- spi bus mosi output
spi_miso_i : in std_logic := 'X'; -- spi bus spi_miso_i input
---- parallel interface ----
di_req_o : out std_logic; -- preload lookahead data request line
di_i : in std_logic_vector (N-1 downto 0) := (others => 'X'); -- parallel data in (clocked on rising spi_clk after last bit)
wren_i : in std_logic := 'X'; -- user data write enable, starts transmission when interface is idle
wr_ack_o : out std_logic; -- write acknowledge
do_valid_o : out std_logic; -- do_o data valid signal, valid during one spi_clk rising edge.
do_o : out std_logic_vector (N-1 downto 0); -- parallel output (clocked on rising spi_clk after last bit)
--- debug ports: can be removed or left unconnected for the application circuit ---
sck_ena_o : out std_logic; -- debug: internal sck enable signal
sck_ena_ce_o : out std_logic; -- debug: internal sck clock enable signal
do_transfer_o : out std_logic; -- debug: internal transfer driver
wren_o : out std_logic; -- debug: internal state of the wren_i pulse stretcher
rx_bit_reg_o : out std_logic; -- debug: internal rx bit
state_dbg_o : out std_logic_vector (3 downto 0); -- debug: internal state register
core_clk_o : out std_logic;
core_n_clk_o : out std_logic;
core_ce_o : out std_logic;
core_n_ce_o : out std_logic;
sh_reg_dbg_o : out std_logic_vector (N-1 downto 0) -- debug: internal shift register
);
end spi_master;
--================================================================================================================
-- this architecture is a pipelined register-transfer description.
-- all signals are clocked at the rising edge of the system clock 'sclk_i'.
--================================================================================================================
architecture rtl of spi_master is
-- core clocks, generated from 'sclk_i': initialized at GSR to differential values
signal core_clk : std_logic := '0'; -- continuous core clock, positive logic
signal core_n_clk : std_logic := '1'; -- continuous core clock, negative logic
signal core_ce : std_logic := '0'; -- core clock enable, positive logic
signal core_n_ce : std_logic := '1'; -- core clock enable, negative logic
-- spi bus clock, generated from the CPOL selected core clock polarity
signal spi_2x_ce : std_logic := '1'; -- spi_2x clock enable
signal spi_clk : std_logic := '0'; -- spi bus output clock
signal spi_clk_reg : std_logic; -- output pipeline delay for spi sck (do NOT global initialize)
-- core fsm clock enables
signal fsm_ce : std_logic := '1'; -- fsm clock enable
signal sck_ena_ce : std_logic := '1'; -- SCK clock enable
signal samp_ce : std_logic := '1'; -- data sampling clock enable
--
-- GLOBAL RESET:
-- all signals are initialized to zero at GSR (global set/reset) by giving explicit
-- initialization values at declaration. This is needed for all Xilinx FPGAs, and
-- especially for the Spartan-6 and newer CLB architectures, where a async reset can
-- reduce the usability of the slice registers, due to the need to share the control
-- set (RESET/PRESET, CLOCK ENABLE and CLOCK) by all 8 registers in a slice.
-- By using GSR for the initialization, and reducing async RESET local init to the bare
-- essential, the model achieves better LUT/FF packing and CLB usability.
--
-- internal state signals for register and combinatorial stages
signal state_next : natural range N+1 downto 0 := 0;
signal state_reg : natural range N+1 downto 0 := 0;
-- shifter signals for register and combinatorial stages
signal sh_next : std_logic_vector (N-1 downto 0);
signal sh_reg : std_logic_vector (N-1 downto 0);
-- input bit sampled buffer
signal rx_bit_reg : std_logic := '0';
-- buffered di_i data signals for register and combinatorial stages
signal di_reg : std_logic_vector (N-1 downto 0);
-- internal wren_i stretcher for fsm combinatorial stage
signal wren : std_logic;
signal wr_ack_next : std_logic := '0';
signal wr_ack_reg : std_logic := '0';
-- internal SSEL enable control signals
signal ssel_ena_next : std_logic := '0';
signal ssel_ena_reg : std_logic := '0';
-- internal SCK enable control signals
signal sck_ena_next : std_logic;
signal sck_ena_reg : std_logic;
-- buffered do_o data signals for register and combinatorial stages
signal do_buffer_next : std_logic_vector (N-1 downto 0);
signal do_buffer_reg : std_logic_vector (N-1 downto 0);
-- internal signal to flag transfer to do_buffer_reg
signal do_transfer_next : std_logic := '0';
signal do_transfer_reg : std_logic := '0';
-- internal input data request signal
signal di_req_next : std_logic := '0';
signal di_req_reg : std_logic := '0';
-- cross-clock do_transfer_reg -> do_valid_o_reg pipeline
signal do_valid_A : std_logic := '0';
signal do_valid_B : std_logic := '0';
signal do_valid_C : std_logic := '0';
signal do_valid_D : std_logic := '0';
signal do_valid_next : std_logic := '0';
signal do_valid_o_reg : std_logic := '0';
-- cross-clock di_req_reg -> di_req_o_reg pipeline
signal di_req_o_A : std_logic := '0';
signal di_req_o_B : std_logic := '0';
signal di_req_o_C : std_logic := '0';
signal di_req_o_D : std_logic := '0';
signal di_req_o_next : std_logic := '1';
signal di_req_o_reg : std_logic := '1';
signal clk_cnt : std_logic_vector(3 downto 0);
begin
--=============================================================================================
-- GENERICS CONSTRAINTS CHECKING
--=============================================================================================
-- minimum word width is 8 bits
assert N >= 8
report "Generic parameter 'N' (shift register size) needs to be 8 bits minimum"
severity FAILURE;
-- minimum prefetch lookahead check
assert PREFETCH >= 1
report "Generic parameter 'PREFETCH' (lookahead count) needs to be 1 minimum"
severity FAILURE;
-- maximum prefetch lookahead check
assert PREFETCH <= N-5
report "Generic parameter 'PREFETCH' (lookahead count) out of range, needs to be N-5 maximum"
severity FAILURE;
-- SPI_2X_CLK_DIV clock divider value must not be zero
assert SPI_2X_CLK_DIV > 0
report "Generic parameter 'SPI_2X_CLK_DIV' must not be zero"
severity FAILURE;
--=============================================================================================
-- CLOCK GENERATION
--=============================================================================================
-- In order to preserve global clocking resources, the core clocking scheme is completely based
-- on using clock enables to process the serial high-speed clock at lower rates for the core fsm,
-- the spi clock generator and the input sampling clock.
-- The clock generation block derives 2 continuous antiphase signals from the 2x spi base clock
-- for the core clocking.
-- The 2 clock phases are generated by separate and synchronous FFs, and should have only
-- differential interconnect delay skew.
-- Clock enable signals are generated with the same phase as the 2 core clocks, and these clock
-- enables are used to control clocking of all internal synchronous circuitry.
-- The clock enable phase is selected for serial input sampling, fsm clocking, and spi SCK output,
-- based on the configuration of CPOL and CPHA.
-- Each phase is selected so that all the registers can be clocked with a rising edge on all SPI
-- modes, by a single high-speed global clock, preserving clock resources and clock to data skew.
-----------------------------------------------------------------------------------------------
-- generate the 2x spi base clock enable from the serial high-speed input clock
spi_2x_ce_gen_proc: process (sclk_i) is
-- variable clk_cnt : integer range SPI_2X_CLK_DIV-1 downto 0 := 0;
begin
if sclk_i'event and sclk_i = '1' then
if clk_cnt = SPI_2X_CLK_DIV-1 then
spi_2x_ce <= '1';
clk_cnt <= "0000";
else
spi_2x_ce <= '0';
clk_cnt <= clk_cnt + 1;
end if;
end if;
end process spi_2x_ce_gen_proc;
-----------------------------------------------------------------------------------------------
-- generate the core antiphase clocks and clock enables from the 2x base CE.
core_clock_gen_proc : process (sclk_i) is
begin
if sclk_i'event and sclk_i = '1' then
if spi_2x_ce = '1' then
-- generate the 2 antiphase core clocks
core_clk <= core_n_clk;
core_n_clk <= not core_n_clk;
-- generate the 2 phase core clock enables
core_ce <= core_n_clk;
core_n_ce <= not core_n_clk;
else
core_ce <= '0';
core_n_ce <= '0';
end if;
end if;
end process core_clock_gen_proc;
-- --=============================================================================================
-- -- GENERATE BLOCKS
-- --=============================================================================================
-- -- spi clk generator: generate spi_clk from core_clk depending on CPOL
-- spi_sck_cpol_0_proc: if CPOL = '0' generate
-- begin
-- spi_clk <= core_clk; -- for CPOL=0, spi clk has idle LOW
-- end generate;
--
-- spi_sck_cpol_1_proc: if CPOL = '1' generate
-- begin
-- spi_clk <= core_n_clk; -- for CPOL=1, spi clk has idle HIGH
-- end generate;
-- -----------------------------------------------------------------------------------------------
-- -- Sampling clock enable generation: generate 'samp_ce' from 'core_ce' or 'core_n_ce' depending on CPHA
-- -- always sample data at the half-cycle of the fsm update cell
-- samp_ce_cpha_0_proc: if CPHA = '0' generate
-- begin
-- samp_ce <= core_ce;
-- end generate;
--
-- samp_ce_cpha_1_proc: if CPHA = '1' generate
-- begin
-- samp_ce <= core_n_ce;
-- end generate;
-- -----------------------------------------------------------------------------------------------
-- -- FSM clock enable generation: generate 'fsm_ce' from core_ce or core_n_ce depending on CPHA
-- fsm_ce_cpha_0_proc: if CPHA = '0' generate
-- begin
-- fsm_ce <= core_n_ce; -- for CPHA=0, latch registers at rising edge of negative core clock enable
-- end generate;
--
-- fsm_ce_cpha_1_proc: if CPHA = '1' generate
-- begin
-- fsm_ce <= core_ce; -- for CPHA=1, latch registers at rising edge of positive core clock enable
-- end generate;
with CPOL select
spi_clk <= core_clk when '0',
core_n_clk when others;
with CPHA select
samp_ce <= core_ce when '0',
core_n_ce when others;
with CPHA select
fsm_ce <= core_ce when '1',
core_n_ce when others;
-----------------------------------------------------------------------------------------------
-- sck enable control: control sck advance phase for CPHA='1' relative to fsm clock
sck_ena_ce <= core_n_ce; -- for CPHA=1, SCK is advanced one-half cycle
--=============================================================================================
-- REGISTERED INPUTS
--=============================================================================================
-- rx bit flop: capture rx bit after SAMPLE edge of sck
rx_bit_proc : process (sclk_i, spi_miso_i) is
begin
if sclk_i'event and sclk_i = '1' then
if samp_ce = '1' then
rx_bit_reg <= spi_miso_i;
end if;
end if;
end process rx_bit_proc;
--=============================================================================================
-- CROSS-CLOCK PIPELINE TRANSFER LOGIC
--=============================================================================================
-- do_valid_o and di_req_o strobe output logic
-- this is a delayed pulse generator with a ripple-transfer FFD pipeline, that generates a
-- fixed-length delayed pulse for the output flags, at the parallel clock domain
out_transfer_proc : process ( pclk_i, do_transfer_reg, di_req_reg,
do_valid_A, do_valid_B, do_valid_D,
di_req_o_A, di_req_o_B, di_req_o_D ) is
begin
if pclk_i'event and pclk_i = '1' then -- clock at parallel port clock
-- do_transfer_reg -> do_valid_o_reg
do_valid_A <= do_transfer_reg; -- the input signal must be at least 2 clocks long
do_valid_B <= do_valid_A; -- feed it to a ripple chain of FFDs
do_valid_C <= do_valid_B;
do_valid_D <= do_valid_C;
do_valid_o_reg <= do_valid_next; -- registered output pulse
--------------------------------
-- di_req_reg -> di_req_o_reg
di_req_o_A <= di_req_reg; -- the input signal must be at least 2 clocks long
di_req_o_B <= di_req_o_A; -- feed it to a ripple chain of FFDs
di_req_o_C <= di_req_o_B;
di_req_o_D <= di_req_o_C;
di_req_o_reg <= di_req_o_next; -- registered output pulse
end if;
-- generate a 2-clocks pulse at the 3rd clock cycle
do_valid_next <= do_valid_A and do_valid_B and not do_valid_D;
di_req_o_next <= di_req_o_A and di_req_o_B and not di_req_o_D;
end process out_transfer_proc;
-- parallel load input registers: data register and write enable
in_transfer_proc: process ( pclk_i, wren_i, wr_ack_reg ) is
begin
-- registered data input, input register with clock enable
if pclk_i'event and pclk_i = '1' then
if wren_i = '1' then
di_reg <= di_i; -- parallel data input buffer register
end if;
end if;
-- stretch wren pulse to be detected by spi fsm (ffd with sync preset and sync reset)
if pclk_i'event and pclk_i = '1' then
if wren_i = '1' then -- wren_i is the sync preset for wren
wren <= '1';
elsif wr_ack_reg = '1' then -- wr_ack is the sync reset for wren
wren <= '0';
end if;
end if;
end process in_transfer_proc;
--=============================================================================================
-- REGISTER TRANSFER PROCESSES
--=============================================================================================
-- fsm state and data registers: synchronous to the spi base reference clock
core_reg_proc : process (sclk_i) is
begin
-- FF registers clocked on rising edge and cleared on sync rst_i
if sclk_i'event and sclk_i = '1' then
if rst_i = '1' then -- sync reset
state_reg <= 0; -- only provide local reset for the state machine
elsif fsm_ce = '1' then -- fsm_ce is clock enable for the fsm
state_reg <= state_next; -- state register
end if;
end if;
-- FF registers clocked synchronous to the fsm state
if sclk_i'event and sclk_i = '1' then
if fsm_ce = '1' then
sh_reg <= sh_next; -- shift register
ssel_ena_reg <= ssel_ena_next; -- spi select enable
do_buffer_reg <= do_buffer_next; -- registered output data buffer
do_transfer_reg <= do_transfer_next; -- output data transferred to buffer
di_req_reg <= di_req_next; -- input data request
wr_ack_reg <= wr_ack_next; -- write acknowledge for data load synchronization
end if;
end if;
-- FF registers clocked one-half cycle earlier than the fsm state
if sclk_i'event and sclk_i = '1' then
if sck_ena_ce = '1' then
sck_ena_reg <= sck_ena_next; -- spi clock enable: look ahead logic
end if;
end if;
end process core_reg_proc;
--=============================================================================================
-- COMBINATORIAL LOGIC PROCESSES
--=============================================================================================
-- state and datapath combinatorial logic
core_combi_proc : process ( sh_reg, state_reg, rx_bit_reg, ssel_ena_reg, sck_ena_reg, do_buffer_reg,
do_transfer_reg, wr_ack_reg, di_req_reg, di_reg, wren ) is
begin
sh_next <= sh_reg; -- all output signals are assigned to (avoid latches)
ssel_ena_next <= ssel_ena_reg; -- controls the slave select line
sck_ena_next <= sck_ena_reg; -- controls the clock enable of spi sck line
do_buffer_next <= do_buffer_reg; -- output data buffer
do_transfer_next <= do_transfer_reg; -- output data flag
wr_ack_next <= wr_ack_reg; -- write acknowledge
di_req_next <= di_req_reg; -- prefetch data request
spi_mosi_o <= sh_reg(N-1); -- default to avoid latch inference
state_next <= state_reg; -- next state
case state_reg is
when (N+1) => -- this state is to enable SSEL before SCK
spi_mosi_o <= sh_reg(N-1); -- shift out tx bit from the MSb
ssel_ena_next <= '1'; -- tx in progress: will assert SSEL
sck_ena_next <= '1'; -- enable SCK on next cycle (stays off on first SSEL clock cycle)
di_req_next <= '0'; -- prefetch data request: deassert when shifting data
wr_ack_next <= '0'; -- remove write acknowledge for all but the load stages
state_next <= state_reg - 1; -- update next state at each sck pulse
when (N) => -- deassert 'di_rdy' and stretch do_valid
spi_mosi_o <= sh_reg(N-1); -- shift out tx bit from the MSb
di_req_next <= '0'; -- prefetch data request: deassert when shifting data
sh_next(N-1 downto 1) <= sh_reg(N-2 downto 0); -- shift inner bits
sh_next(0) <= rx_bit_reg; -- shift in rx bit into LSb
wr_ack_next <= '0'; -- remove write acknowledge for all but the load stages
state_next <= state_reg - 1; -- update next state at each sck pulse
when (N-1) downto (PREFETCH+3) => -- remove 'do_transfer' and shift bits
spi_mosi_o <= sh_reg(N-1); -- shift out tx bit from the MSb
di_req_next <= '0'; -- prefetch data request: deassert when shifting data
do_transfer_next <= '0'; -- reset 'do_valid' transfer signal
sh_next(N-1 downto 1) <= sh_reg(N-2 downto 0); -- shift inner bits
sh_next(0) <= rx_bit_reg; -- shift in rx bit into LSb
wr_ack_next <= '0'; -- remove write acknowledge for all but the load stages
state_next <= state_reg - 1; -- update next state at each sck pulse
when (PREFETCH+2) downto 2 => -- raise prefetch 'di_req_o' signal
spi_mosi_o <= sh_reg(N-1); -- shift out tx bit from the MSb
di_req_next <= '1'; -- request data in advance to allow for pipeline delays
sh_next(N-1 downto 1) <= sh_reg(N-2 downto 0); -- shift inner bits
sh_next(0) <= rx_bit_reg; -- shift in rx bit into LSb
wr_ack_next <= '0'; -- remove write acknowledge for all but the load stages
state_next <= state_reg - 1; -- update next state at each sck pulse
when 1 => -- transfer rx data to do_buffer and restart if new data is written
spi_mosi_o <= sh_reg(N-1); -- shift out tx bit from the MSb
di_req_next <= '1'; -- request data in advance to allow for pipeline delays
do_buffer_next(N-1 downto 1) <= sh_reg(N-2 downto 0); -- shift rx data directly into rx buffer
do_buffer_next(0) <= rx_bit_reg; -- shift last rx bit into rx buffer
do_transfer_next <= '1'; -- signal transfer to do_buffer
if wren = '1' then -- load tx register if valid data present at di_i
state_next <= N; -- next state is top bit of new data
sh_next <= di_reg; -- load parallel data from di_reg into shifter
sck_ena_next <= '1'; -- SCK enabled
wr_ack_next <= '1'; -- acknowledge data in transfer
else
sck_ena_next <= '0'; -- SCK disabled: tx empty, no data to send
wr_ack_next <= '0'; -- remove write acknowledge for all but the load stages
state_next <= state_reg - 1; -- update next state at each sck pulse
end if;
when 0 => -- idle state: start and end of transmission
di_req_next <= '1'; -- will request data if shifter empty
sck_ena_next <= '0'; -- SCK disabled: tx empty, no data to send
if wren = '1' then -- load tx register if valid data present at di_i
spi_mosi_o <= di_reg(N-1); -- special case: shift out first tx bit from the MSb (look ahead)
ssel_ena_next <= '1'; -- enable interface SSEL
state_next <= N+1; -- start from idle: let one cycle for SSEL settling
sh_next <= di_reg; -- load bits from di_reg into shifter
wr_ack_next <= '1'; -- acknowledge data in transfer
else
spi_mosi_o <= sh_reg(N-1); -- shift out tx bit from the MSb
ssel_ena_next <= '0'; -- deassert SSEL: interface is idle
wr_ack_next <= '0'; -- remove write acknowledge for all but the load stages
state_next <= 0; -- when idle, keep this state
end if;
when others =>
state_next <= 0; -- state 0 is safe state
end case;
end process core_combi_proc;
--=============================================================================================
-- OUTPUT LOGIC PROCESSES
--=============================================================================================
-- data output processes
spi_ssel_o_proc: spi_ssel_o <= not ssel_ena_reg; -- active-low slave select line
do_o_proc: do_o <= do_buffer_reg; -- parallel data out
do_valid_o_proc: do_valid_o <= do_valid_o_reg; -- data out valid
di_req_o_proc: di_req_o <= di_req_o_reg; -- input data request for next cycle
wr_ack_o_proc: wr_ack_o <= wr_ack_reg; -- write acknowledge
-----------------------------------------------------------------------------------------------
-- SCK out logic: pipeline phase compensation for the SCK line
-----------------------------------------------------------------------------------------------
-- This is a MUX with an output register.
-- The register gives us a pipeline delay for the SCK line, pairing with the state machine moore
-- output pipeline delay for the MOSI line, and thus enabling higher SCK frequency.
spi_sck_o_gen_proc : process (sclk_i, sck_ena_reg, spi_clk, spi_clk_reg) is
begin
if sclk_i'event and sclk_i = '1' then
if sck_ena_reg = '1' then
spi_clk_reg <= spi_clk; -- copy the selected clock polarity
else
spi_clk_reg <= CPOL; -- when clock disabled, set to idle polarity
end if;
end if;
spi_sck_o <= spi_clk_reg; -- connect register to output
end process spi_sck_o_gen_proc;
--=============================================================================================
-- DEBUG LOGIC PROCESSES
--=============================================================================================
-- these signals are useful for verification, and can be deleted after debug.
do_transfer_proc: do_transfer_o <= do_transfer_reg;
state_dbg_proc: state_dbg_o <= std_logic_vector(to_unsigned(state_reg, 4));
rx_bit_reg_proc: rx_bit_reg_o <= rx_bit_reg;
wren_o_proc: wren_o <= wren;
sh_reg_dbg_proc: sh_reg_dbg_o <= sh_reg;
core_clk_o_proc: core_clk_o <= core_clk;
core_n_clk_o_proc: core_n_clk_o <= core_n_clk;
core_ce_o_proc: core_ce_o <= core_ce;
core_n_ce_o_proc: core_n_ce_o <= core_n_ce;
sck_ena_o_proc: sck_ena_o <= sck_ena_reg;
sck_ena_ce_o_proc: sck_ena_ce_o <= sck_ena_ce;
end architecture rtl;
Pour mettre tout cela dans le processeur, nous allons naturellement commencer par modifier le fichier io.vhd.
Modification du fichier io.vhd
[modifier | modifier le wikicode]Voici le fichier modifié : il gère un peu trop de périphériques pour cette section mais nous le laissons tel quel.
-------------------------------------------------------------------------------
--
-- 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;
-- SPI signals
--SlaveSelect is managed with a PORT
SCLK : out std_logic;
MOSI : out std_logic;
MISO : in 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 SPCR : std_logic_vector(7 downto 0) := X"2D";
constant SPSR : std_logic_vector(7 downto 0) := X"2E";
constant SPDR : std_logic_vector(7 downto 0) := X"2F";
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 particulière 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;
component spi_master is
Generic (
N : positive := 32; -- 32bit serial word length is default
-- CPOL : std_logic := '0'; -- SPI mode selection (mode 0 default)
-- CPHA : std_logic := '0'; -- CPOL = clock polarity, CPHA = clock phase.
PREFETCH : positive := 2); -- prefetch lookahead cycles
-- SPI_2X_CLK_DIV : positive := 5); -- for a 100MHz sclk_i, yields a 10MHz SCK
Port (
sclk_i : in std_logic := 'X'; -- high-speed serial interface system clock
pclk_i : in std_logic := 'X'; -- high-speed parallel interface system clock
rst_i : in std_logic := 'X'; -- reset core
CPOL : std_logic := '0'; -- SPI mode selection (mode 0 default)
CPHA : std_logic := '0'; -- CPOL = clock polarity, CPHA = clock phase.
SPI_2X_CLK_DIV : std_logic_vector(3 downto 0):="0000"; -- for a 100MHz sclk_i, yields a 10MHz SCK
---- serial interface ----
spi_ssel_o : out std_logic; -- spi bus slave select line
spi_sck_o : out std_logic; -- spi bus sck
spi_mosi_o : out std_logic; -- spi bus mosi output
spi_miso_i : in std_logic := 'X'; -- spi bus spi_miso_i input
---- parallel interface ----
di_req_o : out std_logic; -- preload lookahead data request line
di_i : in std_logic_vector (N-1 downto 0) := (others => 'X'); -- parallel data in (clocked on rising spi_clk after last bit)
wren_i : in std_logic := 'X'; -- user data write enable, starts transmission when interface is idle
wr_ack_o : out std_logic; -- write acknowledge
do_valid_o : out std_logic; -- do_o data valid signal, valid during one spi_clk rising edge.
do_o : out std_logic_vector (N-1 downto 0); -- parallel output (clocked on rising spi_clk after last bit)
--- debug ports: can be removed or left unconnected for the application circuit ---
sck_ena_o : out std_logic; -- debug: internal sck enable signal
sck_ena_ce_o : out std_logic; -- debug: internal sck clock enable signal
do_transfer_o : out std_logic; -- debug: internal transfer driver
wren_o : out std_logic; -- debug: internal state of the wren_i pulse stretcher
rx_bit_reg_o : out std_logic; -- debug: internal rx bit
state_dbg_o : out std_logic_vector (3 downto 0); -- debug: internal state register
core_clk_o : out std_logic;
core_n_clk_o : out std_logic;
core_ce_o : out std_logic;
core_n_ce_o : out std_logic;
sh_reg_dbg_o : out std_logic_vector (N-1 downto 0) -- debug: internal shift register
);
end component spi_master;
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_PORTC : std_logic_vector(7 downto 0);
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);
-- SPI master core
signal s_SPSR_SPIF : std_logic := '0';
signal s_SPSR_AGAIN : std_logic := '0';
signal s_wr_SPDR : std_logic := '0';
signal wr_SPDR : std_logic := '0';
signal s_SPDR : std_logic_vector(7 downto 0) := x"00";
signal s_SPDR_in : std_logic_vector(7 downto 0) := x"00";
signal s_CPOL : std_logic := '0'; -- SPI mode selection (mode 0 default)
signal s_CPHA : std_logic := '0'; -- CPOL = clock polarity, CPHA = clock phase.
signal s_SPI_2X_CLK_DIV : std_logic_vector(3 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 particulière 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
);
spi: spi_master Generic map(
N => 8) -- 32bit serial word length is default
-- SPI_2X_CLK_DIV => 10) -- for a 25MHz sclk_i, yields a 1.25 MHz SCK
Port map (
sclk_i => I_clk, -- high-speed serial interface system clock
pclk_i => I_clk, -- high-speed parallel interface system clock
rst_i => I_CLR, -- reset core
CPOL => s_CPOL, -- SPI mode selection (mode 0 default)
CPHA => s_CPHA, -- CPOL = clock polarity, CPHA = clock phase.
SPI_2X_CLK_DIV => s_SPI_2X_CLK_DIV,
---- serial interface ----
spi_ssel_o => s_SPSR_SPIF, -- spi bus slave select line
spi_sck_o => SCLK, -- spi bus sck
spi_mosi_o => MOSI, -- spi bus mosi output
spi_miso_i => MISO, -- spi bus spi_miso_i input
---- parallel interface ----
di_req_o => s_SPSR_AGAIN, -- preload lookahead data request line
di_i => s_SPDR, -- parallel data in (clocked on rising spi_clk after last bit)
wren_i => s_wr_SPDR, -- user data write enable, starts transmission when interface is idle
wr_ack_o => open, -- write acknowledge
do_valid_o => open, -- do_o data valid signal, valid during one spi_clk rising edge.
do_o => s_SPDR_in, -- parallel output (clocked on rising spi_clk after last bit)
--- debug ports: can be removed or left unconnected for the application circuit ---
sck_ena_o => open, -- debug: internal sck enable signal
sck_ena_ce_o => open, -- debug: internal sck clock enable signal
do_transfer_o => open, -- debug: internal transfer driver
wren_o => open, -- debug: internal state of the wren_i pulse stretcher
rx_bit_reg_o => open, -- debug: internal rx bit
state_dbg_o => open, -- debug: internal state register
core_clk_o => open,
core_n_clk_o => open,
core_ce_o => open,
core_n_ce_o => open,
sh_reg_dbg_o => open -- debug: internal shift register
);
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 particulière
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 PORTC => Q_DOUT <= S_PORTC; -- PORTC
-- writing in SPDR will
when SPDR => Q_DOUT <= s_SPDR_in;
when SPSR => Q_DOUT <= s_SPSR_SPIF & s_SPSR_AGAIN & "0000" & s_SPI_2X_CLK_DIV(3 downto 2);
--allow use of & and | operators on [3:0] :
when SPCR => Q_DOUT <= "0000"& s_CPOL & s_CPHA & s_SPI_2X_CLK_DIV(1 downto 0);
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
S_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 SPDR => s_SPDR <= I_DIN;
when SPCR => s_CPOL <= I_DIN(3);
s_CPHA <= I_DIN(2);
s_SPI_2X_CLK_DIV(1 downto 0) <= I_DIN(1 downto 0);
when SPSR => s_SPI_2X_CLK_DIV(3 downto 2) <= I_DIN(1 downto 0);
when others =>
end case;
end if;
end if;
end process;
Q_PORTC <= S_PORTC;
-- delayed signal for SPI core
wr_SPDR <= I_WE_IO when (I_ADR_IO = SPDR) else '0'; -- write SPDR
process(I_CLK) begin
if (rising_edge(I_CLK)) then
s_wr_SPDR <= wr_SPDR;
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
--> removed 2011/10/18
-- Q_LEDS(1) <= L_LEDS;
-- Q_LEDS(0) <= not L_LEDS;
--<
Q_INTVEC <= L_INTVEC;
end Behavioral;
Ce fichier se trouvant à l'intérieur du microcontrôleur il faut bien sûr aussi modifier le fichier correspondant au microconctroleur.
Modification du fichier de description du microcontrôleur
[modifier | modifier le wikicode]Voici le fichier modifié : il gère lui aussi un peu trop de périphériques pour cette section mais nous le laissons tel quel.
-------------------------------------------------------------------------------
--
-- 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: avr_fpga - Behavioral
-- Create Date: 13:51:24 11/07/2009
-- Description: top level of a CPU
--
-------------------------------------------------------------------------------
-- This version is specific for Spartan 3 Digilent starter kit
-- modified 2011/10/18 : Serge Moutou
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity atmega16 is
port ( I_CLK_100 : in std_logic; -- instead of I_CLK_50
I_PINB : in std_logic_vector(7 downto 0);
I_RX : in std_logic;
I_CLR : in std_logic;
Q_PORTD : out std_logic_vector(7 downto 0);
Q_PORTB : out std_logic_vector(7 downto 0);
Q_PORTC : 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;
-- SPI signals
--SlaveSelect is managed with a PORT
SCLK : out std_logic;
MOSI : out std_logic;
MISO : in std_logic
);
end atmega16;
architecture Behavioral of atmega16 is
component cpu_core
port ( I_CLK : in std_logic;
I_CLR : in std_logic;
I_INTVEC : in std_logic_vector( 5 downto 0);
I_DIN : in std_logic_vector( 7 downto 0);
Q_OPC : out std_logic_vector(15 downto 0);
Q_PC : out std_logic_vector(15 downto 0);
Q_DOUT : out std_logic_vector( 7 downto 0);
Q_ADR_IO : out std_logic_vector( 7 downto 0);
Q_RD_IO : out std_logic;
Q_WE_IO : out std_logic);
end component;
--signal C_PC : std_logic_vector(15 downto 0);
--signal C_OPC : std_logic_vector(15 downto 0);
signal C_ADR_IO : std_logic_vector( 7 downto 0);
signal C_DOUT : std_logic_vector( 7 downto 0);
signal C_RD_IO : std_logic;
signal C_WE_IO : std_logic;
component io
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_RD_IO : in std_logic;
I_WE_IO : in std_logic;
I_PINB : in std_logic_vector( 7 downto 0);
I_RX : 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);
Q_PORTC : out std_logic_vector( 7 downto 0);
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;
-- SPI signals
--SlaveSelect is managed with a PORT
SCLK : out std_logic;
MOSI : out std_logic;
MISO : in std_logic
);
end component;
signal N_INTVEC : std_logic_vector( 5 downto 0);
signal N_DOUT : std_logic_vector( 7 downto 0);
signal L_CLK_50 : std_logic := '0'; --50 MHz
--signal L_CLK_12_5 : std_logic := '0'; --12.5 MHz
signal L_CLK_CNT : std_logic_vector( 2 downto 0) := "000";
signal L_CLR : std_logic; -- reset, active low
signal L_CLR_N : std_logic := '0'; -- reset, active low
signal L_C1_N : std_logic := '0'; -- switch debounce, active low
signal L_C2_N : std_logic := '0'; -- switch debounce, active low
begin
cpu : cpu_core
port map( I_CLK => L_CLK_50,--L_CLK_25,--L_CLK_12_5, --L_CLK, -- 25 MHz
I_CLR => I_CLR,
I_DIN => N_DOUT,
I_INTVEC => N_INTVEC,
Q_ADR_IO => C_ADR_IO,
Q_DOUT => C_DOUT,
-- not used in this project :
Q_OPC => open, --C_OPC,
Q_PC => open, --C_PC,
Q_RD_IO => C_RD_IO,
Q_WE_IO => C_WE_IO);
-- !!! doesn't work with different clocks for both component !!!!
ino : io
port map( I_CLK => L_CLK_50, --50MHz
I_CLR => I_CLR,
-->
I_ADR_IO => C_ADR_IO,
I_DIN => C_DOUT,
I_RD_IO => C_RD_IO,
I_RX => I_RX,
I_PINB => I_PINB,
I_WE_IO => C_WE_IO,
Q_PORTB => Q_PORTB,
Q_DOUT => N_DOUT,
Q_INTVEC => N_INTVEC,
Q_PORTC => Q_PORTC,
--> added 2011/10/18
Q_PORTD => Q_PORTD,
--<
Q_TX => Q_TX,
tm1637scl => tm1637scl,
tm1637sda => tm1637sda,
SDA => SDA,
SCL => SCL,
MOSI => MOSI,
MISO => MISO,
SCLK => SCLK);
-- input clock scaler
--
clk_div : process(I_CLK_100,l_clk_cnt) --I_CLK_50 : 50 MHz
begin
if (rising_edge(I_CLK_100)) then
L_CLK_CNT <= L_CLK_CNT + "001";
end if;
L_CLK_50 <= L_CLK_CNT(0); -- added for processor
-- L_CLK_12_5 <= L_CLK_CNT(1);-- added for 19200 bauds but removed
end process;
-- reset button debounce process
--
-- deb : process(L_CLK_25)
-- begin
-- if (rising_edge(L_CLK_25)) then
-- -- switch debounce
-- if ((I_SWITCH(8) = '0') or (I_SWITCH(9) = '0')) then -- pushed
-- L_CLR_N <= '0';
-- L_C2_N <= '0';
-- L_C1_N <= '0';
-- else -- released
-- L_CLR_N <= L_C2_N;
-- L_C2_N <= L_C1_N;
-- L_C1_N <= '1';
-- end if;
-- end if;
-- end process;
L_CLR_N <= I_CLR;
end Behavioral;
Il ne vous manque plus que le fichier de contraintes.
Fichier de contraintes
[modifier | modifier le wikicode]Le fichier de contraintes est naturellement très dépendant des cartes FPGA utilisées. Pour le travail de cette section, nous avons choisi une carte chinoise achetée autour de 20 € (2018) comportant un spartan6LX9. Pour ce prix, vous n'avez naturellement pas de programmateur, il faut donc l'acheter en plus. Les programmateur pour Xilinx sont autour de 20 € contrairement à ceux d'Altera qui sont à moins de 8 € (transport compris - 2019).
## Clock signal NET "I_CLK_100" LOC = "P55" | IOSTANDARD = "LVTTL"; Net "I_CLK_100" TNM_NET = sys_clk_pin; TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin 50000 kHz; NET "I_CLR" LOC = "P14" | IOSTANDARD = "LVTTL"; ## Leds NET "Q_PORTC<0>" LOC = "P87" | IOSTANDARD = "LVTTL"; NET "Q_PORTC<1>" LOC = "P88" | IOSTANDARD = "LVTTL"; ## i2c NET "SCL" LOC = "P1" | IOSTANDARD = LVTTL | PULLUP ;#IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L45N_M3ODT, Sch name = JC3 NET "SDA" LOC = "P5" | IOSTANDARD = LVTTL | PULLUP ;#IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L46P_M3CLK, Sch name = JC4 ## i2c-like pour TM1637 NET "tm1637scl" LOC = "P7" | IOSTANDARD = LVTTL;# | PULLUP ; NET "tm1637sda" LOC = "P9" | IOSTANDARD = LVTTL | PULLUP ; ## SPI #SSEL est sur un bit de PORT #SSEL NET "Q_PORTC<2>" LOC = "P142" | IOSTANDARD = "LVTTL"; NET "SCLK" LOC = "P140" | IOSTANDARD = "LVTTL"; NET "MOSI" LOC = "P138" | IOSTANDARD = "LVTTL"; NET "MISO" LOC = "P134" | IOSTANDARD = "LVTTL"; ## ILI9341 = SPI +: # DC DATA/COMMAND NET "Q_PORTC<3>" LOC = "P132" | IOSTANDARD = "LVTTL";#DC NET "Q_PORTC<4>" LOC = "P127" | IOSTANDARD = "LVTTL";#RESET NET "Q_PORTC<5>" LOC = "P124" | IOSTANDARD = "LVTTL";#LED ## Program Memory INST cpu/opcf/pmem/pe_1 LOC = RAMB16_X1Y2; INST cpu/opcf/pmem/pe_0 LOC = RAMB16_X0Y4; INST cpu/opcf/pmem/pe_3 LOC = RAMB16_X1Y6; INST cpu/opcf/pmem/pe_2 LOC = RAMB16_X0Y2; INST cpu/opcf/pmem/po_1 LOC = RAMB16_X1Y0; INST cpu/opcf/pmem/po_0 LOC = RAMB16_X0Y0; INST cpu/opcf/pmem/po_3 LOC = RAMB16_X1Y4; INST cpu/opcf/pmem/po_2 LOC = RAMB16_X0Y6;
Et le logiciel de tout cela
[modifier | modifier le wikicode]Pour faire fonctionner les périphériques de cette section, il faut naturellement un peu de carburant : le logiciel. Nous sommes parti de la librairie utilisée dans la carte Tiva de l'étude avec un microcontrôleur du commerce et l'avons porté pour nos besoins.
#define TM1637_I2C_COMM1 0x40
#define TM1637_I2C_COMM2 0xC0
#define TM1637_I2C_COMM3 0x80
#include <avr/io.h>
//#include <avr/interrupt.h>
#undef F_CPU
#define F_CPU 25000000UL
#include "util/delay.h"
#define TWWR 1
#define TWRD 3
//Basic Colors
#define RED 0xf800
#define GREEN 0x07e0
#define BLUE 0x001f
#define BLACK 0x0000
#define YELLOW 0xffe0
#define WHITE 0xffff
//Other Colors
#define CYAN 0x07ff
#define BRIGHT_RED 0xf810
#define GRAY1 0x8410
#define GRAY2 0x4208
//TFT resolution 240*320
#define MIN_X 0
#define MIN_Y 0
#define MAX_X 239
#define MAX_Y 319
#define FONT_SPACE 6
#define FONT_X 8
#define FONT_Y 8
const uint8_t simpleFont[][8] =
{
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x5F,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x07,0x00,0x07,0x00,0x00,0x00},
{0x00,0x14,0x7F,0x14,0x7F,0x14,0x00,0x00},
{0x00,0x24,0x2A,0x7F,0x2A,0x12,0x00,0x00},
{0x00,0x23,0x13,0x08,0x64,0x62,0x00,0x00},
{0x00,0x36,0x49,0x55,0x22,0x50,0x00,0x00},
{0x00,0x00,0x05,0x03,0x00,0x00,0x00,0x00},
{0x00,0x1C,0x22,0x41,0x00,0x00,0x00,0x00},
{0x00,0x41,0x22,0x1C,0x00,0x00,0x00,0x00},
{0x00,0x08,0x2A,0x1C,0x2A,0x08,0x00,0x00},
{0x00,0x08,0x08,0x3E,0x08,0x08,0x00,0x00},
{0x00,0xA0,0x60,0x00,0x00,0x00,0x00,0x00},
{0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x00},
{0x00,0x60,0x60,0x00,0x00,0x00,0x00,0x00},
{0x00,0x20,0x10,0x08,0x04,0x02,0x00,0x00},
{0x00,0x3E,0x51,0x49,0x45,0x3E,0x00,0x00},
{0x00,0x00,0x42,0x7F,0x40,0x00,0x00,0x00},
{0x00,0x62,0x51,0x49,0x49,0x46,0x00,0x00},
{0x00,0x22,0x41,0x49,0x49,0x36,0x00,0x00},
{0x00,0x18,0x14,0x12,0x7F,0x10,0x00,0x00},
{0x00,0x27,0x45,0x45,0x45,0x39,0x00,0x00},
{0x00,0x3C,0x4A,0x49,0x49,0x30,0x00,0x00},
{0x00,0x01,0x71,0x09,0x05,0x03,0x00,0x00},
{0x00,0x36,0x49,0x49,0x49,0x36,0x00,0x00},
{0x00,0x06,0x49,0x49,0x29,0x1E,0x00,0x00},
{0x00,0x00,0x36,0x36,0x00,0x00,0x00,0x00},
{0x00,0x00,0xAC,0x6C,0x00,0x00,0x00,0x00},
{0x00,0x08,0x14,0x22,0x41,0x00,0x00,0x00},
{0x00,0x14,0x14,0x14,0x14,0x14,0x00,0x00},
{0x00,0x41,0x22,0x14,0x08,0x00,0x00,0x00},
{0x00,0x02,0x01,0x51,0x09,0x06,0x00,0x00},
{0x00,0x32,0x49,0x79,0x41,0x3E,0x00,0x00},
{0x00,0x7E,0x09,0x09,0x09,0x7E,0x00,0x00},
{0x00,0x7F,0x49,0x49,0x49,0x36,0x00,0x00},
{0x00,0x3E,0x41,0x41,0x41,0x22,0x00,0x00},
{0x00,0x7F,0x41,0x41,0x22,0x1C,0x00,0x00},
{0x00,0x7F,0x49,0x49,0x49,0x41,0x00,0x00},
{0x00,0x7F,0x09,0x09,0x09,0x01,0x00,0x00},
{0x00,0x3E,0x41,0x41,0x51,0x72,0x00,0x00},
{0x00,0x7F,0x08,0x08,0x08,0x7F,0x00,0x00},
{0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00},
{0x00,0x20,0x40,0x41,0x3F,0x01,0x00,0x00},
{0x00,0x7F,0x08,0x14,0x22,0x41,0x00,0x00},
{0x00,0x7F,0x40,0x40,0x40,0x40,0x00,0x00},
{0x00,0x7F,0x02,0x0C,0x02,0x7F,0x00,0x00},
{0x00,0x7F,0x04,0x08,0x10,0x7F,0x00,0x00},
{0x00,0x3E,0x41,0x41,0x41,0x3E,0x00,0x00},
{0x00,0x7F,0x09,0x09,0x09,0x06,0x00,0x00},
{0x00,0x3E,0x41,0x51,0x21,0x5E,0x00,0x00},
{0x00,0x7F,0x09,0x19,0x29,0x46,0x00,0x00},
{0x00,0x26,0x49,0x49,0x49,0x32,0x00,0x00},
{0x00,0x01,0x01,0x7F,0x01,0x01,0x00,0x00},
{0x00,0x3F,0x40,0x40,0x40,0x3F,0x00,0x00},
{0x00,0x1F,0x20,0x40,0x20,0x1F,0x00,0x00},
{0x00,0x3F,0x40,0x38,0x40,0x3F,0x00,0x00},
{0x00,0x63,0x14,0x08,0x14,0x63,0x00,0x00},
{0x00,0x03,0x04,0x78,0x04,0x03,0x00,0x00},
{0x00,0x61,0x51,0x49,0x45,0x43,0x00,0x00},
{0x00,0x7F,0x41,0x41,0x00,0x00,0x00,0x00},
{0x00,0x02,0x04,0x08,0x10,0x20,0x00,0x00},
{0x00,0x41,0x41,0x7F,0x00,0x00,0x00,0x00},
{0x00,0x04,0x02,0x01,0x02,0x04,0x00,0x00},
{0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00},
{0x00,0x01,0x02,0x04,0x00,0x00,0x00,0x00},
{0x00,0x20,0x54,0x54,0x54,0x78,0x00,0x00},
{0x00,0x7F,0x48,0x44,0x44,0x38,0x00,0x00},
{0x00,0x38,0x44,0x44,0x28,0x00,0x00,0x00},
{0x00,0x38,0x44,0x44,0x48,0x7F,0x00,0x00},
{0x00,0x38,0x54,0x54,0x54,0x18,0x00,0x00},
{0x00,0x08,0x7E,0x09,0x02,0x00,0x00,0x00},
{0x00,0x18,0xA4,0xA4,0xA4,0x7C,0x00,0x00},
{0x00,0x7F,0x08,0x04,0x04,0x78,0x00,0x00},
{0x00,0x00,0x7D,0x00,0x00,0x00,0x00,0x00},
{0x00,0x80,0x84,0x7D,0x00,0x00,0x00,0x00},
{0x00,0x7F,0x10,0x28,0x44,0x00,0x00,0x00},
{0x00,0x41,0x7F,0x40,0x00,0x00,0x00,0x00},
{0x00,0x7C,0x04,0x18,0x04,0x78,0x00,0x00},
{0x00,0x7C,0x08,0x04,0x7C,0x00,0x00,0x00},
{0x00,0x38,0x44,0x44,0x38,0x00,0x00,0x00},
{0x00,0xFC,0x24,0x24,0x18,0x00,0x00,0x00},
{0x00,0x18,0x24,0x24,0xFC,0x00,0x00,0x00},
{0x00,0x00,0x7C,0x08,0x04,0x00,0x00,0x00},
{0x00,0x48,0x54,0x54,0x24,0x00,0x00,0x00},
{0x00,0x04,0x7F,0x44,0x00,0x00,0x00,0x00},
{0x00,0x3C,0x40,0x40,0x7C,0x00,0x00,0x00},
{0x00,0x1C,0x20,0x40,0x20,0x1C,0x00,0x00},
{0x00,0x3C,0x40,0x30,0x40,0x3C,0x00,0x00},
{0x00,0x44,0x28,0x10,0x28,0x44,0x00,0x00},
{0x00,0x1C,0xA0,0xA0,0x7C,0x00,0x00,0x00},
{0x00,0x44,0x64,0x54,0x4C,0x44,0x00,0x00},
{0x00,0x08,0x36,0x41,0x00,0x00,0x00,0x00},
{0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00},
{0x00,0x41,0x36,0x08,0x00,0x00,0x00,0x00},
{0x00,0x02,0x01,0x01,0x02,0x01,0x00,0x00},
{0x00,0x02,0x05,0x05,0x02,0x00,0x00,0x00}
};
//#define WII_NUNCHUK_I2C_ADDRESS 0x52
//******* 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);
void TFT_sendCMD(uint8_t index);
void TFT_WRITE_DATA(uint8_t data);
void TFT_sendData(uint16_t data);
void TFT_WRITE_Package(uint16_t *data, uint8_t howmany);
void TFT_backlight_on(void);
void TFT_backlight_off(void);
uint8_t TFT_Read_Register(uint8_t Addr, uint8_t xParameter);
void TFT_TFTinit (void);
uint8_t TFT_readID(void);
void TFT_setCol(uint16_t StartCol,uint16_t EndCol);
void TFT_setPage(uint16_t StartPage,uint16_t EndPage);
void TFT_fillScreen(uint16_t XL, uint16_t XR, uint16_t YU, uint16_t YD, uint16_t color);
void TFT_fillScreen2(void);
void TFT_setXY(uint16_t poX, uint16_t poY);
void TFT_setPixel(uint16_t poX, uint16_t poY,uint16_t color);
void TFT_drawChar( uint8_t ascii, uint16_t poX, uint16_t poY,uint16_t size, uint16_t fgcolor, uint16_t bgcolor);
void TFT_drawString(char *string,uint16_t poX, uint16_t poY, uint16_t size,uint16_t fgcolor, uint16_t bgcolor);
void TFT_fillRectangle(uint16_t poX, uint16_t poY, uint16_t length, uint16_t width, uint16_t color);
void TFT_drawHorizontalLine( uint16_t poX, uint16_t poY,uint16_t length,uint16_t color);
void TFT_drawLine( uint16_t x0,uint16_t y0,uint16_t x1, uint16_t y1,uint16_t color);
void TFT_drawVerticalLine( uint16_t poX, uint16_t poY, uint16_t length,uint16_t color);
void TFT_drawRectangle(uint16_t poX, uint16_t poY, uint16_t length, uint16_t width,uint16_t color);
void TFT_drawCircle(int poX, int poY, int r,uint16_t color);
void TFT_fillCircle(int poX, int poY, int r,uint16_t color);
void TFT_drawTraingle( int poX1, int poY1, int poX2, int poY2, int poX3, int poY3, uint16_t color);
uint8_t TFT_drawNumber(long long_num,uint16_t poX, uint16_t poY,uint16_t size,uint16_t fgcolor, uint16_t bgcolor);
uint8_t TFT_drawFloat(float floatNumber,uint8_t decimal,uint16_t poX, uint16_t poY,uint16_t size,uint16_t fgcolor, uint16_t bgcolor);
uint8_t SPI_transfer(uint8_t data);
void SPI_init();
int main() {
uint8_t i, t[4];
uint16_t cmpt=0;
// init
SPI_init();
TFT_TFTinit (); //init TFT library
TFT_backlight_on(); // turn on the background light
TFT_drawChar('S',0,0,1,RED,BLACK); // draw char: 'S', (0, 0), size: 1, color: RED
TFT_drawChar('E',10,10,2,BLUE,BLACK); // draw char: 'E', (10, 10), size: 2, color: BLUE
TFT_drawChar('E',20,40,3,GREEN,BLACK); // draw char: 'E', (20, 40), size: 3, color: GREEN
TFT_drawChar('E',30,80,4,YELLOW,BLACK); // draw char: 'E', (30, 80), size: 4, color: YELLOW
TFT_drawChar('D',40,120,4,YELLOW,BLACK); // draw char: 'D', (40, 120), size: 4, color: YELLOW
TFT_drawString("Hello",0,180,3,CYAN,BLACK); // draw string: "hello", (0, 180), size: 3, color: CYAN
TFT_drawString("World!!",60,220,4,WHITE,BLACK); // draw string: "world!!", (80, 230), size: 4, color: WHITE
// loop
while(1) {
// TFT_backlight_on();
// _delay_ms(1000);
// TFT_backlight_off();
// _delay_ms(1000);
// TFT_fillScreen(0, 239, 0, 319, GREEN);
TFT_drawLine(0,0,239,319,RED); //start: (0, 0) end: (239, 319), color : RED
TFT_drawVerticalLine(60,100,100,GREEN); // Draw a vertical line
// start: (60, 100) length: 100 color: green
TFT_drawHorizontalLine(30,60,150,BLUE); //Draw a horizontal line
//start: (30, 60), high: 150, color: blue
}
return 0;
}
void TWIInit(void)
{
//set TWBR=Fcpu/(5*Fscl)
//set SCL to ??kHz
TWBR = 125;
//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);
}
// ne fonctionne pas !!
void TWIStop() {
TWCR = (1<<TWINT)|(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;
}
void TFT_sendCMD(uint8_t index)
{
PORTC &= 0xF7; // CD low
PORTC &= 0xFB; // SSEL=CS = Low
SPI_transfer(index);
PORTC |= 0x04; // SSEL=CS = high
}
void TFT_WRITE_DATA(uint8_t data)
{
PORTC |= 0x08; // CD High
PORTC &= 0xFB; // SSEL=CS = Low
SPI_transfer(data);
PORTC |= 0x04; // SSEL=CS = High
}
void TFT_sendData(uint16_t data)
{
uint8_t data1 = data>>8;
uint8_t data2 = data&0xff;
PORTC |= 0x08; // CD High
PORTC &= 0xFB; // SSEL=CS =Low
SPI_transfer(data1);
SPI_transfer(data2);
PORTC |= 0x04; // SSEL=CS high
}
void TFT_WRITE_Package(uint16_t *data, uint8_t howmany)
{
uint8_t data1 = 0;
uint8_t data2 = 0;
PORTC |= 0x08; // CD High
PORTC &= 0xFB; // SSEL=CS low
uint8_t count=0;
for(count=0;count<howmany;count++)
{
data1 = data[count]>>8;
data2 = data[count]&0xff;
SPI_transfer(data1);
SPI_transfer(data2);
}
PORTC |= 0x04; // SSEL=CS
}
void TFT_backlight_on(void)
{
PORTC |= 0x20;
}
void TFT_backlight_off(void)
{
PORTC &= 0xDF;//LED off
}
uint8_t TFT_Read_Register(uint8_t Addr, uint8_t xParameter)
{
uint8_t data=0;
TFT_sendCMD(0xd9); /* ext command */
TFT_WRITE_DATA(0x10+xParameter);
PORTC &= 0xF7; // CD low /* 0x11 is the first Parameter */
PORTC &= 0xFB; // SSEL=CS low
SPI_transfer(Addr);
PORTC |= 0x08; // CD high
data = SPI_transfer(0);
PORTC |= 0x04; // SSEL=CS high
return data;
}
void TFT_TFTinit (void)
{
PORTC |= 0x04; // SSEL=CS high
PORTC &= 0xFB; // SSEL=CS low
PORTC &= 0xF7; // CD low
PORTC &= 0xDF;//LED off
PORTC &= 0xEF; // RST low
// b3=CPOL, b2=CPHA, b1=SPR1 b0=SPR0
SPCR &= ~((1<<CPOL) | (1<<CPHA)); // mode 0
SPCR |= (1<<SPR1); // poids faible division = 2
SPSR |= (1 << 1); // poids fort division = 8
SPI_transfer(0); // Strawman transfer, fixes USCI issue on G2553
PORTC |= 0x04; // SSEL=CS high ????
PORTC |= 0x08; // CD high
PORTC |= 3; // DEBUG ou cela bloque-t-il ?
uint8_t i=0, TFTDriver=0;
PORTC &= 0xEF; // RST low
_delay_ms(10);
PORTC |= 0x10; // RST high
for(i=0;i<3;i++)
{
TFTDriver = TFT_readID();
}
TFT_sendCMD(0xCB);
TFT_WRITE_DATA(0x39);
TFT_WRITE_DATA(0x2C);
TFT_WRITE_DATA(0x00);
TFT_WRITE_DATA(0x34);
TFT_WRITE_DATA(0x02);
TFT_sendCMD(0xCF);
TFT_WRITE_DATA(0x00);
TFT_WRITE_DATA(0XC1);
TFT_WRITE_DATA(0X30);
TFT_sendCMD(0xE8);
TFT_WRITE_DATA(0x85);
TFT_WRITE_DATA(0x00);
TFT_WRITE_DATA(0x78);
TFT_sendCMD(0xEA);
TFT_WRITE_DATA(0x00);
TFT_WRITE_DATA(0x00);
TFT_sendCMD(0xED);
TFT_WRITE_DATA(0x64);
TFT_WRITE_DATA(0x03);
TFT_WRITE_DATA(0X12);
TFT_WRITE_DATA(0X81);
TFT_sendCMD(0xF7);
TFT_WRITE_DATA(0x20);
TFT_sendCMD(0xC0); //Power control
TFT_WRITE_DATA(0x23); //VRH[5:0]
TFT_sendCMD(0xC1); //Power control
TFT_WRITE_DATA(0x10); //SAP[2:0];BT[3:0]
TFT_sendCMD(0xC5); //VCM control
TFT_WRITE_DATA(0x3e); //Contrast
TFT_WRITE_DATA(0x28);
TFT_sendCMD(0xC7); //VCM control2
TFT_WRITE_DATA(0x86); //--
TFT_sendCMD(0x36); // Memory Access Control
TFT_WRITE_DATA(0x48); //C8 //48 68绔栧睆//28 E8 妯睆
TFT_sendCMD(0x3A);
TFT_WRITE_DATA(0x55);
TFT_sendCMD(0xB1);
TFT_WRITE_DATA(0x00);
TFT_WRITE_DATA(0x18);
TFT_sendCMD(0xB6); // Display Function Control
TFT_WRITE_DATA(0x08);
TFT_WRITE_DATA(0x82);
TFT_WRITE_DATA(0x27);
TFT_sendCMD(0xF2); // 3Gamma Function Disable
TFT_WRITE_DATA(0x00);
TFT_sendCMD(0x26); //Gamma curve selected
TFT_WRITE_DATA(0x01);
TFT_sendCMD(0xE0); //Set Gamma
TFT_WRITE_DATA(0x0F);
TFT_WRITE_DATA(0x31);
TFT_WRITE_DATA(0x2B);
TFT_WRITE_DATA(0x0C);
TFT_WRITE_DATA(0x0E);
TFT_WRITE_DATA(0x08);
TFT_WRITE_DATA(0x4E);
TFT_WRITE_DATA(0xF1);
TFT_WRITE_DATA(0x37);
TFT_WRITE_DATA(0x07);
TFT_WRITE_DATA(0x10);
TFT_WRITE_DATA(0x03);
TFT_WRITE_DATA(0x0E);
TFT_WRITE_DATA(0x09);
TFT_WRITE_DATA(0x00);
TFT_sendCMD(0XE1); //Set Gamma
TFT_WRITE_DATA(0x00);
TFT_WRITE_DATA(0x0E);
TFT_WRITE_DATA(0x14);
TFT_WRITE_DATA(0x03);
TFT_WRITE_DATA(0x11);
TFT_WRITE_DATA(0x07);
TFT_WRITE_DATA(0x31);
TFT_WRITE_DATA(0xC1);
TFT_WRITE_DATA(0x48);
TFT_WRITE_DATA(0x08);
TFT_WRITE_DATA(0x0F);
TFT_WRITE_DATA(0x0C);
TFT_WRITE_DATA(0x31);
TFT_WRITE_DATA(0x36);
TFT_WRITE_DATA(0x0F);
TFT_sendCMD(0x11); //Exit Sleep
_delay_ms(120);
TFT_sendCMD(0x29); //Display on
TFT_sendCMD(0x2c);
TFT_fillScreen2();
}
uint8_t TFT_readID(void)
{
uint8_t i=0;
uint8_t data[3] ;
uint8_t ID[3] = {0x00, 0x93, 0x41};
uint8_t ToF=1;
for(i=0;i<3;i++)
{
data[i]=TFT_Read_Register(0xd3,i+1);
if(data[i] != ID[i])
{
ToF=0;
}
}
return ToF;
}
void TFT_setCol(uint16_t StartCol,uint16_t EndCol)
{
TFT_sendCMD(0x2A); /* Column Command address */
TFT_sendData(StartCol);
TFT_sendData(EndCol);
}
void TFT_setPage(uint16_t StartPage,uint16_t EndPage)
{
TFT_sendCMD(0x2B); /* Column Command address */
TFT_sendData(StartPage);
TFT_sendData(EndPage);
}
void TFT_fillScreen(uint16_t XL, uint16_t XR, uint16_t YU, uint16_t YD, uint16_t color)
{
unsigned long XY=0;
unsigned long i=0;
if(XL > XR)
{
XL = XL^XR;
XR = XL^XR;
XL = XL^XR;
}
if(YU > YD)
{
YU = YU^YD;
YD = YU^YD;
YU = YU^YD;
}
//*********** a redefinir la fonction constrain !!!!!!!!!!!!!!!!!!
//XL = constrain(XL, MIN_X,MAX_X);
//XR = constrain(XR, MIN_X,MAX_X);
//YU = constrain(YU, MIN_Y,MAX_Y);
//YD = constrain(YD, MIN_Y,MAX_Y);
XY = (XR-XL+1);
XY = XY*(YD-YU+1);
TFT_setCol(XL,XR);
TFT_setPage(YU, YD);
TFT_sendCMD(0x2c); /* start to write to display ra */
/* m */
PORTC |= 0x08; // CD high
PORTC &= 0xFB; // SSEL=CS low
uint8_t Hcolor = color>>8;
uint8_t Lcolor = color&0xff;
for(i=0; i < XY; i++)
{
SPI_transfer(Hcolor);
SPI_transfer(Lcolor);
}
PORTC |= 0x04; // SSEL=CS high
}
void TFT_fillScreen2(void)
{
uint16_t i;
TFT_setCol(0, 239);
TFT_setPage(0, 319);
TFT_sendCMD(0x2c); /* start to write to display ra */
/* m */
PORTC |= 0x08; // CD high
PORTC &= 0xFB; // SSEL=CS low
for(i=0; i<38400; i++)
{
SPI_transfer(0);
SPI_transfer(0);
SPI_transfer(0);
SPI_transfer(0);
}
PORTC |= 0x04; // SSEL=CS high
}
void TFT_setXY(uint16_t poX, uint16_t poY)
{
TFT_setCol(poX, poX);
TFT_setPage(poY, poY);
TFT_sendCMD(0x2c);
}
void TFT_setPixel(uint16_t poX, uint16_t poY,uint16_t color)
{
TFT_setXY(poX, poY);
TFT_sendData(color);
}
void TFT_drawChar( uint8_t ascii, uint16_t poX, uint16_t poY,uint16_t size, uint16_t fgcolor, uint16_t bgcolor)
{
//fillRectangle(poX, poY, poX+FONT_X*size, poY+FONT_Y*size, BLACK);
int i;
uint8_t f;
if((ascii>=32)&&(ascii<=127))
{
;
}
else
{
ascii = '?'-32;
}
for (i =0; i<FONT_X; i++ ) {
uint8_t temp = simpleFont[ascii-0x20][i];
for(f=0;f<8;f++)
{
if((temp>>f)&0x01)
{
TFT_fillRectangle(poX+i*size, poY+f*size, size, size, fgcolor);
}
else
TFT_fillRectangle(poX+i*size, poY+f*size, size, size, bgcolor);
}
}
}
void TFT_drawString(char *string,uint16_t poX, uint16_t poY, uint16_t size,uint16_t fgcolor, uint16_t bgcolor)
{
while(*string)
{
TFT_drawChar(*string, poX, poY, size, fgcolor, bgcolor);
*string++;
if(poX < MAX_X)
{
poX += FONT_SPACE*size; /* Move cursor right */
}
}
}
//fillRectangle(poX+i*size, poY+f*size, size, size, fgcolor);
void TFT_fillRectangle(uint16_t poX, uint16_t poY, uint16_t length, uint16_t width, uint16_t color)
{
TFT_fillScreen(poX, poX+length, poY, poY+width, color);
}
void TFT_drawHorizontalLine( uint16_t poX, uint16_t poY,
uint16_t length,uint16_t color)
{
int i;
TFT_setCol(poX,poX + length);
TFT_setPage(poY,poY);
TFT_sendCMD(0x2c);
for(i=0; i<length; i++)
TFT_sendData(color);
}
int abs(int data) {
if (data > 0) return data; else return -data;
}
void TFT_drawLine( uint16_t x0,uint16_t y0,uint16_t x1, uint16_t y1,uint16_t color)
{
int x = x1-x0;
int y = y1-y0;
int dx = abs(x), sx = x0<x1 ? 1 : -1;
int dy = -abs(y), sy = y0<y1 ? 1 : -1;
int err = dx+dy, e2; /* error value e_xy */
for (;;){ /* loop */
TFT_setPixel(x0,y0,color);
e2 = 2*err;
if (e2 >= dy) { /* e_xy+e_x > 0 */
if (x0 == x1) break;
err += dy; x0 += sx;
}
if (e2 <= dx) { /* e_xy+e_y < 0 */
if (y0 == y1) break;
err += dx; y0 += sy;
}
}
}
void TFT_drawVerticalLine( uint16_t poX, uint16_t poY, uint16_t length,uint16_t color)
{
int i;
TFT_setCol(poX,poX);
TFT_setPage(poY,poY+length);
TFT_sendCMD(0x2c);
for(i=0; i<length; i++)
TFT_sendData(color);
}
void TFT_drawRectangle(uint16_t poX, uint16_t poY, uint16_t length, uint16_t width,uint16_t color)
{
TFT_drawHorizontalLine(poX, poY, length, color);
TFT_drawHorizontalLine(poX, poY+width, length, color);
TFT_drawVerticalLine(poX, poY, width,color);
TFT_drawVerticalLine(poX + length, poY, width,color);
}
void TFT_drawCircle(int poX, int poY, int r,uint16_t color)
{
int x = -r, y = 0, err = 2-2*r, e2;
do {
TFT_setPixel(poX-x, poY+y,color);
TFT_setPixel(poX+x, poY+y,color);
TFT_setPixel(poX+x, poY-y,color);
TFT_setPixel(poX-x, poY-y,color);
e2 = err;
if (e2 <= y) {
err += ++y*2+1;
if (-x == y && e2 <= x) e2 = 0;
}
if (e2 > x) err += ++x*2+1;
} while (x <= 0);
}
void TFT_fillCircle(int poX, int poY, int r,uint16_t color)
{
int x = -r, y = 0, err = 2-2*r, e2;
do {
TFT_drawVerticalLine(poX-x, poY-y, 2*y, color);
TFT_drawVerticalLine(poX+x, poY-y, 2*y, color);
e2 = err;
if (e2 <= y) {
err += ++y*2+1;
if (-x == y && e2 <= x) e2 = 0;
}
if (e2 > x) err += ++x*2+1;
} while (x <= 0);
}
void TFT_drawTraingle( int poX1, int poY1, int poX2, int poY2, int poX3, int poY3, uint16_t color)
{
TFT_drawLine(poX1, poY1, poX2, poY2,color);
TFT_drawLine(poX1, poY1, poX3, poY3,color);
TFT_drawLine(poX2, poY2, poX3, poY3,color);
}
uint8_t TFT_drawNumber(long long_num,uint16_t poX, uint16_t poY,uint16_t size,uint16_t fgcolor, uint16_t bgcolor)
{
uint8_t char_buffer[10] = "";
uint8_t i = 0;
uint8_t f = 0;
if (long_num < 0)
{
f=1;
TFT_drawChar('-',poX, poY, size, fgcolor, bgcolor);
long_num = -long_num;
if(poX < MAX_X)
{
poX += FONT_SPACE*size; /* Move cursor right */
}
}
else if (long_num == 0)
{
f=1;
TFT_drawChar('0',poX, poY, size, fgcolor, bgcolor);
return f;
if(poX < MAX_X)
{
poX += FONT_SPACE*size; /* Move cursor right */
}
}
while (long_num > 0)
{
char_buffer[i++] = long_num % 10;
long_num /= 10;
}
f = f+i;
for(; i > 0; i--)
{
TFT_drawChar('0'+ char_buffer[i - 1],poX, poY, size, fgcolor, bgcolor);
if(poX < MAX_X)
{
poX+=FONT_SPACE*size; /* Move cursor right */
}
}
return f;
}
uint8_t TFT_drawFloat(float floatNumber,uint8_t decimal,uint16_t poX, uint16_t poY,uint16_t size,uint16_t fgcolor, uint16_t bgcolor)
{
uint16_t temp=0;
float decy=0.0;
float rounding = 0.5;
uint8_t f=0;
uint8_t i;
if(floatNumber<0.0)
{
TFT_drawChar('-',poX, poY, size, fgcolor, bgcolor);
floatNumber = -floatNumber;
if(poX < MAX_X)
{
poX+=FONT_SPACE*size; /* Move cursor right */
}
f =1;
}
for (i=0; i<decimal; ++i)
{
rounding /= 10.0;
}
floatNumber += rounding;
temp = (uint16_t)floatNumber;
uint8_t howlong=TFT_drawNumber(temp,poX, poY, size, fgcolor, bgcolor);
f += howlong;
if((poX+8*size*howlong) < MAX_X)
{
poX+=FONT_SPACE*size*howlong; /* Move cursor right */
}
if(decimal>0)
{
TFT_drawChar('.',poX, poY, size, fgcolor, bgcolor);
if(poX < MAX_X)
{
poX+=FONT_SPACE*size; /* Move cursor right */
}
f +=1;
}
decy = floatNumber-temp; /* decimal part, 4 */
for(i=0;i<decimal;i++)
{
decy *=10; /* for the next decimal */
temp = decy; /* get the decimal */
TFT_drawNumber(temp,poX, poY, size, fgcolor, bgcolor);
floatNumber = -floatNumber;
if(poX < MAX_X)
{
poX+=FONT_SPACE*size; /* Move cursor right */
}
decy -= temp;
}
f +=decimal;
return f;
}
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 SPI
}
Faites le tri de ce que vous avez besoin pour vos tests.
Reprise du problème sur notre Nexys 3
[modifier | modifier le wikicode]
Nous avons repris ce problème sur une carte Nexyx 3. Peu de changement avec ce qui a été fait mais il nous a fallu un peu de temps pour reprendre. Voici le détail du câblage réalisé sur les connecteurs PMod de la Nexys 3. La couleur et l'ordre des fils n'étant pas visibles sur la photo, nous résumons dans un tableau comment la carte a été câblée en utilisant les connecteurs PMod C et D. Le fichier ucf ne correspond plus à celui qui a été donné ci-dessus pour la bonne et simple raison que nous avons changé de carte. Mais le tableau ci-dessous permet tout portage.
2.8 TFT | SDO/MISO | LED | SCK | SDI/MOSI | DC | RESET | CS | GND | VCC |
---|---|---|---|---|---|---|---|---|---|
Connecteurs PMod (C et D) | JC1 | JC2 | JD3 | JD4 | JD1 | JC3 | JD2 | GND | 3.3V |
VHDL | MISO | Q_PORTC(5) | SCLK | MOSI | Q_PORTC(3) | Q_PORTC(4) | Q_PORTC(2) | XXX | XXX |

Voir aussi plus de détails sur les connecteurs Pmod.
Ressources
[modifier | modifier le wikicode]Convertisseur A/N simple pour Joystick
[modifier | modifier le wikicode]Un article de Guido Nopper dans le magazine Elektor (septembre/octobre 2019) propose la réalisation d'un convertisseur analogique numérique sigma delta (donc simple) dans un CPLD. Nous n'avons pas pu résister à porter son code pour un FPGA. Il était réalisé en schématique Xilinx et c'est comme cela que nous allons le présenter. Comme ce chapitre est destiné aux "shields Arduino", nous allons nous proposer de lire un joystick bon marché (par exemple le Module joystick GT1079).
Principe théorique
[modifier | modifier le wikicode]
Voici un schéma de principe d'un convertisseur analogique numérique à modulation sigma-delta dans la figure ci-contre.
On y distingue un intégrateur, un comparateur, le tout bouclé et suivi d'une bascule D et d'un décimateur.
Lisez la partie Convertisseur Sigma Delta de l'article sur les conversions analogiques numériques dans wikipédia.
La réalisation pratique peut se faire avec deux résistances, un condensateur et une bascule D comme indiqué dans la figure. La sortie sur la bascule D se trouve être la modulation sigma Delta.

La moindre utilisation pratique devra réaliser la démodulation sigma delta et ne sera donc pas composée par une seule bascule D. Il faudra ajouter un certain nombre de composants comme indiqué dans la section suivante.
Notre réalisation
[modifier | modifier le wikicode]Nous avons décidé d'utiliser les outils Xilinx de schématique comme cela a été fait dans Elektor. Notre schéma est pourtant assez différent de celui d'Elektor (suffisamment en tout cas pour respecter tout problème de copyright). Une des raisons fondamentale est que l'article d'Elektor envoie le résultat numérique dans un Convertisseur Analogique Numérique pour une comparaison des deux valeurs analogiques. En ce qui nous concerne, nous avons décidé de sortir en binaire sur les 8 leds disponibles sur notre carte FPGA.
Voici comment nous avons conçu notre convertisseur Analogique numérique sigma-delta.

La bascule D du schéma de principe a été remplacée par deux bascules D en série. C'est naturellement ce que l'on fait quand on a un signal extérieur non synchronisé avec l'horloge du FPGA. Nous pensons cependant qu'ici cela n'est pas une nécessité absolue.
Le compteur CB16CE peut être remplacé par un CB8CE puisque nous prenons sa sortie de poids (7) comme horloge principale. Il faut cependant avoir à l'esprit que ce bit de poids 7 est lié à la valeur du condensateur. Pour information pour une résistance R de 4k7 avec :
- un condensateur de 4n7 cela nécessite de prendre le bit de poids 7
- un condensateur de 47nF cela nécessite de prendre le bit de poids 11
- un condensateur de 470nF cela nécessite de prendre le bit de poids 15
Le premier compteur CB8CE en bas à gauche est destiné à compter le temps de comptage du compteur de résultat qui est le CB8CE en haut à droite. Quand le temps de comptage est terminé, ce compteur est mémorisé dans un registre FD8CE puis remis à 0 pour le comptage suivant.
Exercice 1
[modifier | modifier le wikicode]En cherchant les documentations des compteurs et registres utilisés dans le schéma, on vous demande de refaire un implantation complète en VHDL.
Autre réalisation
[modifier | modifier le wikicode]Un de nos collègue, Gilles Millon, a fait une réalisation directe en VHDL en utilisant des principes assez différents de ce que nous avons fait. Mais des comparaisons des deux montages nous ont montrés des résultats pratiques complètement similaires.
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
entity adc_william2 is port (
clk,rst : in std_logic; -- horloge reference 50 MHz
vc : in std_logic; -- signal tension Vc en entree
qbarre : out std_logic; -- signal
vnum : out std_logic_vector(7 downto 0); -- affichage led
hex0,hex1,hex2,hex3,hex4,hex5 : out std_logic_vector(6 downto 0)); -- affichage 7 segments
end entity;
architecture behavior of adc_william2 is
component dds is port(
clkin : in std_logic;
dn : in std_logic_vector(31 downto 0);
clkout : out std_logic);
end component;
component cptbcd7seg is port (
clk,ud,en,rst : in std_logic;
eno : out std_logic;
q: out std_logic_vector(6 downto 0)
);
end component;
signal clkech,clkaff,q,s_en,s_ud,endiz,encent : std_logic;
signal endizd,encentd : std_logic;
signal n: std_logic_vector(7 downto 0);
signal S_HEX0,S_HEX1,S_HEX2: std_logic_vector(6 downto 0):= "0000001";
signal S_HEX3,S_HEX4,S_HEX5: std_logic_vector(6 downto 0):= "0000001";
signal sS_HEX3,sS_HEX4,sS_HEX5: std_logic_vector(6 downto 0):= "0000001";
signal reg: std_logic_vector(255 downto 0) := (others => '0');
signal cmpt8,res8bits,reg8result : std_logic_vector(7 downto 0);
signal clr, wr, tc : std_logic;
begin
-- generateur horloge echantillonnage : 200KHz... réglable
genefreqech : dds port map ( clkin=> clk, clkout=> clkech, dn => x"010624DD");
-- generateur horloge rafraichissement affichage : 5Hz
genefreqaff : dds port map ( clkin=> clk, clkout=> clkaff, dn => x"000001AD");
-- registres bascule D
bascule : process(clkech) begin
if rising_edge(clkech) then
q<= vc;
qbarre <= not vc;
end if;
end process;
-- registre à decalage 128 bits
process(clkech) begin
if rising_edge(clkech) then
if rst='1' then
reg <= (others=>'0');
else
reg <= reg(254 downto 0) & q;
end if;
end if;
end process;
-- conversion : comptage des bits à 1 dans le registre
-- 0 à 128 maximum
-- compteur binaire sortie sur led
process(clkech) begin
if rising_edge(clkech) then
if rst='1' then
n<= (others=>'0');
else
if (reg(0)='1' and reg(255)='0') then
n <= n + 1;
elsif (reg(0)='0' and reg(255)='1') then
n <= n - 1;
end if;
end if;
end if;
end process;
s_ud <= '1' when (reg(0)='1' and reg(255)='0') else '0';
s_en <= '1' when (reg(0)='1' and reg(255)='0') else
'1' when (reg(0)='0' and reg(255)='1') else
'0';
--DEBUT compteur séquencement wr et clr
process(clkech) begin
if rising_edge(clkech) then
cmpt8 <= cmpt8+1;
end if;
end process;
tc <= '1' when cmpt8 = "11111111" else
'0';
--FIN compteur séquencement wr et clr
-- BASCULES D
process(clkech) begin
if rising_edge(clkech) then
wr <= tc;
clr <= wr;
end if;
end process;
-- DEBUT compteur en,clr pour resultat
process(clkech) begin
if rising_edge(clkech) then
if clr = '1' then
res8bits <= "00000000";
elsif q = '1' then --en relié à q
res8bits <= res8bits + '1';
end if;
end if;
end process;
-- registre résultat
process(clkech) begin
if rising_edge(clkech) then
if wr='1' then
reg8result <= res8bits;
end if;
end if;
end process;
united: cptbcd7seg port map (
clk=>clkech,
rst => clr,
ud=>'1',
en=>q,
eno =>endizd,
q=> S_hex3);
dizained: cptbcd7seg port map (
clk=>clkech,
rst => clr,
ud=>'1',
en=>endizd,
eno =>encentd,
q=> S_hex4);
centained: cptbcd7seg port map (
clk=>clkech,
rst => clr,
ud=>'1',
en=>encentd,
eno =>open,
q=> S_hex5);
-- rafraichissement affichage CAN
process(clkech) begin
if rising_edge(clkech) then
if wr='1' then
--vnum <= n;
ss_HEX3 <= S_HEX3;
ss_HEX4 <= S_HEX4;
ss_HEX5 <= S_HEX5;
end if;
end if;
end process;
-- compteurs BCD cascadable
-- affichage sur 3 digit 7 segment
unite: cptbcd7seg port map (
clk=>clkech,
rst => rst,
ud=>s_ud,
en=>s_en,
eno =>endiz,
q=> S_hex0);
dizaine: cptbcd7seg port map (
clk=>clkech,
rst => rst,
ud=>s_ud,
en=>endiz,
eno =>encent,
q=> S_hex1);
centaine: cptbcd7seg port map (
clk=>clkech,
rst => rst,
ud=>s_ud,
en=>encent,
eno =>open,
q=> S_hex2);
-- rafraichissement affichage CAN
process(clkaff) begin
if rising_edge(clkaff) then
--vnum <= n;
vnum <= reg8result;
HEX0 <= S_HEX0;
HEX1 <= S_HEX1;
HEX2 <= S_HEX2;
hex3 <= ss_hex3;
hex4 <= ss_hex4;
hex5 <= ss_hex5;
end if;
end process;
end behavior;
-- decription oscillateur DDS
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use ieee.numeric_std.all;
entity dds is port(
clkin : in std_logic;
dn : in std_logic_vector(31 downto 0);
clkout : out std_logic);
end entity;
architecture behavior of dds is
signal clk : std_logic;
signal a,b : std_logic_vector(31 downto 0);
begin
-- accumulation dn
b <= ( '0' & a(30 downto 0)) + ( '0' & dn(30 downto 0));
-- registre accumulation
process(clkin) begin
if rising_edge(clkin) then
a <= b;
end if;
end process;
-- horloge rapport cyclique 1/2
process(clkin) begin
if rising_edge(clkin) then
if a(31)='1' then
clk <= not clk;
end if;
end if;
end process;
-- sortie horloge
clkout <= clk;
end behavior;
-- compteur/decompteur BCD cascadable à sortie directe 7 segments
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity cptbcd7seg is port (
clk,ud,en,rst : in std_logic;
eno : out std_logic;
q: out std_logic_vector(6 downto 0)
);
end entity;
architecture behavior of cptbcd7seg is
signal n : std_logic_vector(6 downto 0);
begin
process(clk) begin
if rising_edge(clk) then
if rst='1' then
n<="0000001";
elsif en = '1' then
if ud='1' then --comptage
case n is
-- abcdefg
when "0000001" => n<= "1001111"; -- 0->1
when "1001111" => n<= "0010010"; -- 1->2
when "0010010" => n<= "0000110";
when "0000110" => n<= "1001100";
when "1001100" => n<= "0100100";
when "0100100" => n<= "0100000";
when "0100000" => n<= "0001111";
when "0001111" => n<= "0000000";
when "0000000" => n<= "0000100";
when "0000100" => n<= "0000001";
when others => n<= "0000001";
end case;
else -- decomptage
case n is
-- abcdefg
when "0000001" => n<= "0000100"; -- 0->1
when "1001111" => n<= "0000001"; -- 1->2
when "0010010" => n<= "1001111";
when "0000110" => n<= "0010010";
when "1001100" => n<= "0000110";
when "0100100" => n<= "1001100";
when "0100000" => n<= "0100100";
when "0001111" => n<= "0100000";
when "0000000" => n<= "0001111";
when "0000100" => n<= "0000000";
when others => n<= "0000001";
end case;
end if;
end if;
end if;
end process;
eno <='1' when (en='1' and ud='1' and n="0000100") else
'1' when (en='1' and ud='0' and n="0000001") else
'0';
q(0) <= n(6);
q(1) <= n(5);
q(2) <= n(4);
q(3) <= n(3);
q(4) <= n(2);
q(5) <= n(1);
q(6) <= n(0);
end behavior;
Avec le fichier de contraintes pour la carte Altera DE10-Lite :
# Copyright (C) 2019 Intel Corporation. All rights reserved. # Your use of Intel Corporation's design tools, logic functions # and other software and tools, and any partner logic # functions, and any output files from any of the foregoing # (including device programming or simulation files), and any # associated documentation or information are expressly subject # to the terms and conditions of the Intel Program License # Subscription Agreement, the Intel Quartus Prime License Agreement, # the Intel FPGA IP License Agreement, or other applicable license # agreement, including, without limitation, that your use is for # the sole purpose of programming logic devices manufactured by # Intel and sold by Intel or its authorized distributors. Please # refer to the applicable agreement for further details, at # https://fpgasoftware.intel.com/eula. # Quartus Prime Version 18.1.1 Build 646 04/11/2019 SJ Standard Edition # File: /home/moutou01/delta_sigma/adc_william2.csv # Generated on: Fri Jan 10 11:17:58 2020 # Note: The column header names should not be changed if you wish to import this .csv file into the Quartus Prime software. To,Direction,Location,I/O Bank,VREF Group,Fitter Location,I/O Standard,Reserved,Current Strength,Slew Rate,Differential Pair,Strict Preservation clk,Input,PIN_P11,3,B3_N0,PIN_P11,3.3-V LVCMOS,,,,, hex0[6],Output,PIN_C17,7,B7_N0,PIN_C17,2.5 V,,,,, hex0[5],Output,PIN_D17,7,B7_N0,PIN_D17,2.5 V,,,,, hex0[4],Output,PIN_E16,7,B7_N0,PIN_E16,2.5 V,,,,, hex0[3],Output,PIN_C16,7,B7_N0,PIN_C16,2.5 V,,,,, hex0[2],Output,PIN_C15,7,B7_N0,PIN_C15,2.5 V,,,,, hex0[1],Output,PIN_E15,7,B7_N0,PIN_E15,2.5 V,,,,, hex0[0],Output,PIN_C14,7,B7_N0,PIN_C14,2.5 V,,,,, hex1[6],Output,PIN_B17,7,B7_N0,PIN_B17,2.5 V,,,,, hex1[5],Output,PIN_A18,7,B7_N0,PIN_A18,2.5 V,,,,, hex1[4],Output,PIN_A17,7,B7_N0,PIN_A17,2.5 V,,,,, hex1[3],Output,PIN_B16,7,B7_N0,PIN_B16,2.5 V,,,,, hex1[2],Output,PIN_E18,6,B6_N0,PIN_E18,2.5 V,,,,, hex1[1],Output,PIN_D18,6,B6_N0,PIN_D18,2.5 V,,,,, hex1[0],Output,PIN_C18,7,B7_N0,PIN_C18,2.5 V,,,,, hex2[6],Output,PIN_B22,6,B6_N0,PIN_B22,2.5 V,,,,, hex2[5],Output,PIN_C22,6,B6_N0,PIN_C22,2.5 V,,,,, hex2[4],Output,PIN_B21,6,B6_N0,PIN_B21,2.5 V,,,,, hex2[3],Output,PIN_A21,6,B6_N0,PIN_A21,2.5 V,,,,, hex2[2],Output,PIN_B19,7,B7_N0,PIN_B19,2.5 V,,,,, hex2[1],Output,PIN_A20,7,B7_N0,PIN_A20,2.5 V,,,,, hex2[0],Output,PIN_B20,6,B6_N0,PIN_B20,2.5 V,,,,, hex3[6],Output,PIN_E17,6,B6_N0,PIN_A12,,,,,, hex3[5],Output,PIN_D19,6,B6_N0,PIN_E12,,,,,, hex3[4],Output,PIN_C20,6,B6_N0,PIN_A13,,,,,, hex3[3],Output,PIN_C19,7,B7_N0,PIN_C12,,,,,, hex3[2],Output,PIN_E21,6,B6_N0,PIN_A14,,,,,, hex3[1],Output,PIN_E22,6,B6_N0,PIN_H13,,,,,, hex3[0],Output,PIN_F21,6,B6_N0,PIN_C11,,,,,, hex4[6],Output,PIN_F20,6,B6_N0,PIN_D12,,,,,, hex4[5],Output,PIN_F19,6,B6_N0,PIN_B11,,,,,, hex4[4],Output,PIN_H19,6,B6_N0,PIN_B14,,,,,, hex4[3],Output,PIN_J18,6,B6_N0,PIN_A15,,,,,, hex4[2],Output,PIN_E19,6,B6_N0,PIN_J11,,,,,, hex4[1],Output,PIN_E20,6,B6_N0,PIN_J12,,,,,, hex4[0],Output,PIN_F18,6,B6_N0,PIN_A11,,,,,, hex5[6],Output,PIN_N20,6,B6_N0,PIN_E13,,,,,, hex5[5],Output,PIN_N19,6,B6_N0,PIN_A7,,,,,, hex5[4],Output,PIN_M20,6,B6_N0,PIN_B12,,,,,, hex5[3],Output,PIN_N18,6,B6_N0,PIN_H12,,,,,, hex5[2],Output,PIN_L18,6,B6_N0,PIN_C9,,,,,, hex5[1],Output,PIN_K20,6,B6_N0,PIN_C8,,,,,, hex5[0],Output,PIN_J20,6,B6_N0,PIN_B8,,,,,, qbarre,Output,PIN_AB5,3,B3_N0,PIN_AB5,3.3-V LVCMOS,,,,, rst,Input,PIN_C10,7,B7_N0,PIN_C10,2.5 V,,,,, vc,Input,PIN_AB17,4,B4_N0,PIN_AB17,3.3-V LVCMOS,,,,, vnum[7],Output,PIN_D14,7,B7_N0,PIN_D14,2.5 V,,,,, vnum[6],Output,PIN_E14,7,B7_N0,PIN_E14,2.5 V,,,,, vnum[5],Output,PIN_C13,7,B7_N0,PIN_C13,2.5 V,,,,, vnum[4],Output,PIN_D13,7,B7_N0,PIN_D13,2.5 V,,,,, vnum[3],Output,PIN_B10,7,B7_N0,PIN_B10,2.5 V,,,,, vnum[2],Output,PIN_A10,7,B7_N0,PIN_A10,2.5 V,,,,, vnum[1],Output,PIN_A9,7,B7_N0,PIN_A9,2.5 V,,,,, vnum[0],Output,PIN_A8,7,B7_N0,PIN_A8,2.5 V,,,,,
Exercice 2
[modifier | modifier le wikicode]Nous vous demandons de faire la démarche inverse de l'exercice 1 : vous partez du programme VHDL et l'on vous demande de réaliser le schéma correspondant.
Leds adressables NeoPixel
[modifier | modifier le wikicode]Nous allons étudier les protocoles séries WS2812S et WS2812B destinées aux leds adressables dans cette section.
Notre travail de départ va consister à analyser les publications light ws2812 library part 1 et light ws2812 library part 2.
- Nous allons examiner comment utiliser les données de l'analyseur logique de la partie 1 pour tenter la réalisation d'un périphérique VHDL.
- Nous allons ensuite tenter de faire fonctionner la librairie github:light ws2812 AVR dans notre SOC AVR
- Les deux expériences précédentes nous amèneront à modifier le périphérique VHDL de la première étude pour l'adapter en périphérique. Des commentaires postés sur "light ws2812 library part 2" suggèrent d'utiliser un périphérique SPI pour cela avec de la DMA. Pour nous la DMA sera remplacée d'une manière ou d'une autre par de la logique VHDL.
Voir aussi
[modifier | modifier le wikicode]Lecture de données d'une caméra Pixy 2
[modifier | modifier le wikicode]Nous avons eu l'occasion de réaliser dans ce long chapitre un processeur gérant le SPI. Ainsi nous avons géré un écran SPI 2,8" plus haut, ce qui nous a permis de valider le cœur SPI présent dans notre ATMega16.
Introduction
[modifier | modifier le wikicode]Nous allons partir du règlement de la Coupe de France des IUT GEII 2020 à laquelle nous n'avons pas pu participer pour cause de pandémie. Ce règlement est encore disponible : Règlement 2020. En principe la coupe de France aura lieu en juin 2022 avec un règlement probablement assez similaire. Une vidéo de cette coupe de France qui a bien eu lieu est disponible maintenant.
Une des questions que l'on va se poser est : comment utiliser la caméra Pixy2 pour guider le robot jusqu'à une balle de tennis, le tout avec un FPGA ? On parle de balle de tennis ici mais n'importe quel objet appris par la caméra peut remplacer cette balle ! L'apprentissage sera fait dans PixyMon et non pas à l'aide du processeur/FPGA. Dans tout ce qui suit, nous considérons que l'apprentissage est réalisé. Voir bibliographie de cette section pour de plus amples informations. |
Nous n'avons donc pas l'intention de détailler l'utilisation d'une caméra Pixy 2 dans cette section mais simplement de mettre en œuvre un dialogue entre notre FPGA et la caméra permettant de recevoir des données de la Pixy 2. Ce type de dialogue se réalise facilement avec un Arduino puisqu'il y a même un câble vendu avec, qui permet de relier directement la Pixy 2 au programmateur ICSP de celui-ci. C'est la raison pour laquelle nous avons mis cette section dans ce chapitre : la présence possible d'un Arduino rend la Pixy2 comme un éventuel shield. Ce dialogue avec l'ICSP a une particularité : c'est du SPI sans sélection (SS = Slave Select). Heureusement d'autres protocoles sont possibles et peuvent être choisis dans un moniteur spécifique (PixyMon) :
- SPI avec "Slave Select"
- I2C pas très conseillé car un peu lent
- liaison série
- valeur analogique
La valeur analogique pourrait être exploitée avec le convertisseur sigma/Delta d'une section précédente. Ce serait intéressant si plusieurs données analogiques étaient données en même temps, mais ce n'est pas le cas. Nous préferons explorer maintenant le SPI sans (SS = Slave Select) dans notre FPGA qui nous retournera au moins quatre valeurs exploitables :
- position x et y du centre de la balle (ou plus exactement de son cadre rectangulaire ce qui en principe est très souvent identique)
- grandeur Dx et Dy du cadre rectangulaire
Les deux dernières données permettent une évaluation de la distance de la balle tandis que les deux premières permettent une évaluation angulaire du cap.
Réalisation logicielle
[modifier | modifier le wikicode]Nous allons commencer par décrire le protocole d'échange entre la caméra et le FPGA. Nous allons utiliser le protocole SPI sans Slave Select. Pour basculer au SPI avec "Slave Select" il y a deux choses à faire :
- côté Pixy2 il suffit d'utiliser PixyMon pour basculer dans ce mode
- côté FPGA, il suffit de prévoir un bit de PORT et gérer par vous-même. C'est comme cela dans les microcontrôleurs aussi.
Protocole d'échange
[modifier | modifier le wikicode]La connaissance du protocole d'échange entre la caméra et votre processeur est importante. Ce protocole ne dépend pas de la façon matérielle choisie : i2c, SPI sans SS, SPI avec SS, rs232. Voici comment il est documenté dans le document officiel (très partiellement traduit ici). Nous allons examiner par exemple le cas de la recherche de la résolution de la caméra.
- La demande réalisée par votre processeur doit être conforme à la requête standard pour ce type de données :
- Requête
Byte | Description | Value(s) |
0 - 1 | 16-bit sync | 174, 193 (0xc1ae) |
2 | Type de paquet | 12 |
3 | Longueur de données | 1 |
4 | Type (non utilisé réservé pour les versions futures) | 0 - 255 |
Cette suite d'octets est obligatoirement à envoyer si vous voulez obtenir la réponse correspondant à la résolution de l'image en pixels de la part de la caméra Pixy2. Cette résolution pouvant être réglée à l'aide de PixyMon, il vous faut être sûr de la connaître si vous voulez positionner correctement un objet (par rapport à vous) dont les coordonnées vous sont retournées par la caméra.
- La réponse de la caméra doit être conforme à :
- Réponse
Byte | Description | Value(s) |
0 - 1 | 16-bit sync | 175, 193 (0xc1af) |
2 | Type de paquet | 13 |
3 | Longueur des données | 2 |
4 - 5 | 16-bit checksum | sum of payload bytes |
6 - 7 | 16-bit Largeur d'image (en pixels) | 0 - 511 |
8 - 9 | 16-bit Hauteur d'image(en pixels) | 0 - 511 |
Nous allons maintenant nous intéresser à la façon de gérer ce type de données en langage C. Deux façons nous viennent à l'esprit :
- Utilisation d'un type structure en C
/**** déclaration du type avec une structure ****/
/**** correspond au premier tableau ci-dessus ****/
struct getRes {
uint16_t _16bitsSync;
uint8_t TofPacket;
uint8_t LenOfPayload;
uint8_t unUsed;
};
/**** déclaration avec initialisation d'une variable ****/
struct getRes requestRes = {0xC1AE,12,1,0};
- Utilisation d'un tableau
/**** déclaration avec initialisation du tableau ****/
uint8_t ResData[5]={0xAE,0xC1,12,1,0};
Les deux méthodes ont leurs avantages et inconvénients. L'intérêt d'une structure est qu'elle permet d'accéder aux champs directement par leurs noms. Son inconvénient est qu'en général les sous-programmes utilisés pour envoyer ou recevoir des données utilisent des tableaux. Il faudra donc transtyper et ceci vous vaudra un warning de la part du compilateur.
L'inconvénient du tableau est qu'il ne faut pas vous tromper pour mettre les données : regardez comment est mis la valeur 0xC1AE dans le tableau. Vous aurez évidemment le même problème pour retrouver les données.
Nous vous laissons choisir.
Exercice
[modifier | modifier le wikicode]Examinez dans la documentation anglaise appropriée le protocole d'échange pour getResolution() et pour getBlocks(sigmap, maxBlocks) et construisez des types de données pour réaliser correctement ces protocoles ainsi que les sous-programmes correspondants. Testez.
Indication La lecture de ce problème dans un autre cours vous apprendra que le secret de la réussite, nous l'avons d'abord trouvé en utilisant un Arduino. Ensuite seulement, il nous a été très aisé de le porter pour notre SOC/FPGA. Il suffisait d'écrire un sous-programme qui attende l'entête correspondante à la réponse. Une fois réalisé sur Arduino, il nous a fallu quelques minutes pour le faire fonctionner dans notre FPGA.
Nous donnons en vrac du code fonctionnel utilisant le protocole SPI avec Slave Select dans un processeur embarqué dans un FPGA.
#include <avr/io.h>
//#include <avr/interrupt.h>
#undef F_CPU
#define F_CPU 50000000UL
#include "util/delay.h"
//#define DEBUG
uint8_t SPI_transfer(uint8_t data);
void SPI_init();
void SPI_send_Data8(uint8_t data);
void SPI_sendData8XX(uint8_t *data, uint8_t howmany);
void SPI_recieveData8XX(uint8_t *data, uint8_t howmany);
void SPI_sendData16(uint16_t data);
void SPI_sendData16XX(uint16_t *data, uint8_t howmany);
void SPI_recieveData16XX(uint16_t *data, uint8_t howmany);
uint8_t SPI_recieveExpectedData8XX(uint8_t *data, uint8_t howmany);
void usart_init(void);
void usart_send(unsigned char ch);
char usart_receive(void);
void usart_gets(char str[]);
void usart_puts(char str[]);
struct getRes {
uint16_t _16bitsSync;
uint8_t TofPacket;
uint8_t LenOfPayload;
uint8_t unUsed;
};
struct Result {
uint16_t _16bitsSync;
uint8_t TofPacket;
uint8_t LenOfPayload;
uint16_t checkSum;
uint16_t width;
uint16_t height;
};
struct Result resolution;
struct getRes requestRes = {0xC1AE,12,1,0};
struct ReqInfoCCC { // Request Color Connected Components
uint16_t _16bitsSync;
uint8_t TofPacket;
uint8_t LenOfPayload;
uint8_t sigmap;
uint8_t maxBlock;
};
struct ReqInfoCCC reqCCCBlocks = {0xC1AE,32,2,1,1};
struct AnsInfoCCC {
uint16_t _16bitsSync;
uint8_t TofPacket; //33
uint8_t LenOfPayload; //14
uint16_t checkSum;
uint16_t colorCodeNumber;
uint16_t centerX;
uint16_t centerY;
uint16_t width;
uint16_t height;
uint16_t angleColor;
uint8_t trackingIndex;
uint8_t age;
};
#ifdef DEBUG
void usart_putsData(uint8_t data[],uint8_t howMany){
uint8_t i,digit;
for (i=0;i<howMany;i++) {
digit = data[i] >> 4; if (digit <10) usart_send(digit + '0'); else usart_send(digit - 10 + 'A');
digit = data[i] & 0x0F; if (digit <10) usart_send(digit + '0'); else usart_send(digit - 10 + 'A');
usart_send('-');
}
usart_send(0x0D);usart_send(0x0A);
}
#endif
int main() {
// uint8_t i;
struct AnsInfoCCC infoCCC;
uint16_t resolutionX=0,dataCenter=0;
// init
usart_init();
SPI_init();
#ifdef DEBUG
usart_putsData(&requestRes, 5);
_delay_ms(2000);
#endif
SPI_sendData8XX(&requestRes, 5);
//_delay_us(10);
SPI_recieveExpectedData8XX(&resolution,10);
#ifdef DEBUG
usart_putsData(&resolution, 5);
_delay_ms(2000);
#endif
resolutionX=0;
resolutionX = resolution.width;
usart_puts("Largeur en pixels : ");
//usart_send(resolutionX/1000+'0');
usart_send(resolutionX/100+'0');usart_send((resolutionX%100)/10+'0');usart_send(resolutionX%10+'0');
usart_send(0x0D);usart_send(0x0A);
#ifdef DEBUG
while(1);
#endif
// loop
while(1) {
SPI_sendData8XX(&reqCCCBlocks, 6);
_delay_us(10);
SPI_recieveExpectedData8XX(&infoCCC,20);
#ifdef DEBUG
usart_putsData(&infoCCC,20);
_delay_ms(2000);
#endif
dataCenter = infoCCC.centerX;
usart_puts("Position X : ");
usart_send(dataCenter/100+'0');usart_send((dataCenter%100)/10+'0');usart_send(dataCenter%10+'0');
usart_send(0x0D);usart_send(0x0A);
dataCenter = infoCCC.centerY;
usart_puts("Position Y : ");
usart_send(dataCenter/100+'0');usart_send((dataCenter%100)/10+'0');usart_send(dataCenter%10+'0');
usart_send(0x0D);usart_send(0x0A);
_delay_ms(500);
}
return 0;
}
void SPI_init() {
uint8_t digit;
SPCR = 0;
#ifdef DEBUG2
usart_puts("Avant : SPCR = 0x");
digit = SPCR >> 4; if (digit <10) usart_send(digit + '0'); else usart_send(digit - 10 + 'A');
digit = SPCR & 0x0F; if (digit <10) usart_send(digit + '0'); else usart_send(digit - 10 + 'A');
#endif
//SPCR |= (1<<CPHA); // mode 1
SPCR = (1<<CPHA)|(1<<CPOL); // mode 3
SPCR |= (1<<SPE); //enable inutile !!!
SPCR |= 3; // division horloge
//SPCR |= (1<<SPR1); // poids faible division = 2
SPSR |= (1 << 1); // poids fort division = 8
//SPSR |= 3;
#ifdef DEBUG2
usart_puts(" - Après : SPCR = 0x");
digit = SPCR >> 4; if (digit <10) usart_send(digit + '0'); else usart_send(digit - 10 + 'A');
digit = SPCR & 0x0F; if (digit <10) usart_send(digit + '0'); else usart_send(digit - 10 + 'A');
#endif
usart_send(0x0D);usart_send(0x0A);
PORTC |= 0x01; // SSEL=CS = High
}
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(2);
return SPDR;
}
void SPI_send_Data8(uint8_t data){
PORTC &= 0xFE; // SSEL=CS = Low
SPI_transfer(data);
PORTC |= 0x01; // SSEL=CS = High
}
void SPI_sendData8XX(uint8_t *data, uint8_t howmany)
{
uint8_t data1 = 0;
PORTC &= 0xFE; // SSEL=CS low
uint8_t count=0;
for(count=0;count<howmany;count++)
{
data1 = data[count];
SPI_transfer(data1);
}
PORTC |= 0x01; // SSEL=CS high
}
void SPI_recieveData8XX(uint8_t *data, uint8_t howmany){
uint8_t data1 = 0;
PORTC &= 0xFE; // SSEL=CS low
uint8_t count=0;
for(count=0;count<howmany;count++)
{
data1= SPI_transfer(0x00);
data[count] = data1;
}
PORTC |= 0x01; // SSEL=CS high
}
uint8_t SPI_recieveExpectedData8XX(uint8_t *data, uint8_t howmany){
uint8_t data1 = 0, waitingTries=0;
uint8_t count=0;
PORTC &= 0xFE; // SSEL=CS low
// wait for answer OxAF
do {
data1= SPI_transfer(0x00);
if (data1 == 0xAF) {
data[count] = data1;
count++;
} else {
waitingTries++;
}
if (waitingTries > 50) {
PORTC |= 0x01; // SSEL=CS high
return 0;
}
} while(data1 != 0xAF);
// wait for answer OxC1
do {
data1= SPI_transfer(0x00);
if (data1 == 0xC1) {
data[count] = data1;
count++;
} else {
waitingTries++;
}
if (waitingTries > 50) return 0;
} while(data1 != 0xC1);
for(count=2;count<howmany;count++)
{
data1= SPI_transfer(0x00);
data[count] = data1;
}
PORTC |= 0x01; // SSEL=CS high
return count;
}
void SPI_sendData16(uint16_t data)
{
uint8_t data1 = data>>8;
uint8_t data2 = data&0xff;
PORTC &= 0xFE; // SSEL=CS =Low
SPI_transfer(data1);
SPI_transfer(data2);
PORTC |= 0x01; // SSEL=CS high
}
void SPI_sendData16XX(uint16_t *data, uint8_t howmany)
{
uint8_t data1 = 0;
uint8_t data2 = 0;
PORTC &= 0xFE; // SSEL=CS low
uint8_t count=0;
for(count=0;count<howmany;count++)
{
data1 = data[count]>>8;
data2 = data[count]&0xff;
SPI_transfer(data1);
SPI_transfer(data2);
}
PORTC |= 0x01; // SSEL=CS
}
void SPI_recieveData16XX(uint16_t *data, uint8_t howmany) {
uint8_t data1 = 0;
uint8_t data2 = 0;
PORTC &= 0xFE; // SSEL=CS low
uint8_t count=0;
for(count=0;count<howmany;count++)
{
data1= SPI_transfer(0x00);
data2= SPI_transfer(0x00);
data[count] = data1;
data[count] <<= 8;
data[count] += data2;
}
PORTC |= 0x01; // SSEL=CS
}
//************************************************************************
// function usart_puts()
// purpose: put characters in first rs232 PORT
// arguments:
// corresponding string
// return:
// note: 38400,8,n,2 hard coded : transmission
//************************************************************************
void usart_puts(char str[]){
uint8_t i=0;
do {
usart_send(str[i]);
i++;
} while(str[i]!=0);
}
//************************************************************************
// 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
//************************************************************************
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
//************************************************************************
char usart_receive(void){
while (!(UCSRA & (1<<RXC))); //attente tant que Data Present en réception
return UDR;
}
// function usart_gets()
// purpose: recupere l'angle a traiter par cordic au format
// +/- x.xxxx en decimal depuis un envoi par GTKTerm via la liaison serie RS232
// teste la validite de la chaine entree et demande de recommencer s'il y a erreur de format :
// teste le signe + ou-
// test la virgule
// test si les chiffres (0 -9) en sont bien
void usart_gets(char str[]) {
uint8_t i=0;
uint8_t j=0;
uint8_t test_str=1; // flag de validite
char str_msg[]={"\nAngle incorrect!\nVeuillez recommencer en respectant +x.xxxx ou -x.xxxx\n"};
char car;
while(i!=7) {
car=usart_receive(); // saisie de 7 caractères
str[i]=car;
i++;
if (i==7){ // teste la chaine en fin de saisie
test_str=1; // a priori ok
if (str[0]=='+'||str[0]=='-')//test le signe
{test_str=1;}
else {test_str=0;}
if (str[2]!= '.') // test la virgule
{test_str=0;}
if (str[1]<'0' || str[1]>'1') // test la partie entiere
{test_str=0;}
for (j=3;j<7;j++) { // teste la partie decimale
if (str[j]<'0' || str[j]>'9')
{test_str=0;}
}
if (test_str==0) { // verifie en fin de test si la chaine est ok
usart_puts(str_msg);
}
}
}
}
Comme vous pouvez le distinguer dans ce code, le SPI utilisé gère le "Slave Select". En toute franchise, pour le moment, nous avons testé ce code avec le "Slave Select" câblé mais avec la Pixy2 configurée (dans PixyMon) pour ne pas utiliser celui-ci. Nous testerons au plus vite l'utilisation complète de SPI avec "Slave Select". Ce que nous chercherons à savoir c'est dans quel mode faut-il configurer le périphérique SPI : un ou trois comme ici ?
Voir aussi
[modifier | modifier le wikicode]- Communication Pixy2/Arduino dans un autre projet.
- (en) PixyCAM UART Interface (document PDF)
- (en) Comment apprendre à reconnaître des objets à la caméra Pixy2
- (en) Guide de communication avec la Pixy2
Voir aussi
[modifier | modifier le wikicode]- Le shield multifonction
- Shield LCD KeyPad
- Document en français sur les afficheurs LCD
- Voir comment utiliser le "Pin Planer" ou l' "Assignement Editor" pour réaliser des entrées sorties pour l'i2c.
- Pendule inversé dans wikipédia et (en) Inverted pendulum
- Capteurs d'accélération : Accéléromètres
- Equilibre sur deux roues d'un robot dans un autre projet
- sainsmart balancing robot kit
- How to build SainSmart Instabots (3 steps)
- How to build SainSmart Instabots (9 steps)
À regarder pour compléter ce chapitre
[modifier | modifier le wikicode]- afficheur 0,96 pouces LCD OLED de 128 x 64 série I2C et un exemple logiciel