Very High Speed Integrated Circuit Hardware Description Language/Améliorer l'ATMega8 avec l'ATMega16 et l'ATMega32
Nous avons l'intention dans ce chapitre de reprendre ce qui a été fait dans le chapitre précédent avec l'AVR ATMega8 de manière un peu plus systématique. Il doit être clair pour les lecteurs assidus de ce précédent chapitre, qu’il a été rédigé avec aucun recul. Ainsi plusieurs versions de l'ATMega8 ont été utilisées essentiellement pour la gestion des mémoires. Voici donc présentés les défauts du précédent chapitre que nous espérons corriger.
- Une seule version du processeur de base devra être utilisée contrairement aux deux versions du chapitre précédent (premier défaut à corriger)
- Nous n'avons pas, d’autre part, étudié la réalisation systématique des périphériques. C'est aussi ce deuxième défaut que nous espérons corriger dans ce chapitre
- La mémoire programme disponible dans la dernière version de l'ATMega8 était de 16 ko, donc exactement celle qui est présente dans l'ATMega16. Il y avait donc 8 ko inutilisables avec l'option de compilation ATMega8. Nous avons l'intention de les récupérer maintenant (troisième défaut à corriger)
- Enfin, la mémoire RAM disponible dans la dernière version de l'ATMega8 était de 4 ko. Celle qui est disponible dans un ATMega16 est de 1 ko et celle de l'ATMega32 est de 2 ko. Les options de compilations pour ATMega32 nous permettrons donc de récupérer 1 ko facilement. Mais les 2 ko restants seront à gérer à la main avec des pointeurs si l’on désire les utiliser.
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 l'ensemble des corrections qui utilisent mon site perso seront indisponibles à partir de cette date pour tout ce chapitre. SergeMoutou (discuter) |
Passage de l'ATMega8 vers l'ATMega16
[modifier | modifier le wikicode]Comme nous l'avons déjà exprimé en introduction, tout ce qui concerne la gestion des mémoires programme et mémoires RAM est déjà réalisé. La seule grande évolution dans l'ATMega16 est l'apparition d'une instruction de saut nouvelle dans la table de vecteur d'interruptions. Nous allons donc examiner maintenant ce problème. Côté périphérique il y a des différences mais nous n'en avons pas (ou peu) dans notre processeur (des périphériques). C'est à nous de les imaginer.
Changer la gestion des vecteurs d'interruptions
[modifier | modifier le wikicode]Nous avons déjà évoqué ce problème dans la section Ajoutons une interruption du précédent chapitre.
Pour faire simple il vous faut donc regarder dans le fichier opc_deco.vhd autour de la ligne 99, et avoir :
Q_JADR <= "0000000000" & I_OPC(4 downto 0) & "0";
au lieu de
Q_JADR <= "00000000000" & I_OPC(4 downto 0);
pour notre ancien ATMega8.
Faites cela et vous avez un ATMega16 disponible. Cette simplicité de transformation est liée au fait que la seule nouvelle instruction dans l'ATMega16 (à part BREAK comme visible dans cette annexe d'un autre livre) est "JMP". Notre ATMega8 disposait déjà de l'instruction "jmp" et des 16 ko de mémoire programme.
Changer les options de compilation
[modifier | modifier le wikicode]La compilation a toujours été réalisée avec l'option "-mmcu=atmega8" dans le chapitre précédent. Il suffit de compiler maintenant avec l'option "-mmcu=atmega16".
Voici un moyen automatique de compiler sous Linux :
#!/bin/bash
#export PATH=$PATH:/usr/local/avr/bin:~/XILINX/Xilinx/11.1/ISE/bin/lin/
export PATH=$PATH:/usr/bin:/opt/Xilinx/14.5/ISE_DS/ISE/bin/lin64
avr-gcc -g -mmcu=atmega16 -Wall -Os -c your_program.c
avr-gcc -g -mmcu=atmega16 -o your_program.elf -Wl,-Map,your_program.map your_program.o
avr-objdump -h -S your_program.elf > your_program.lss
#avr-objcopy -O binary -R .eeprom cordic.elf cordic.bin
#avr-objcopy -R .eeprom -O ihex hello.elf hello.hex
data2mem -bm memoryS6.bmm -bd your_program.elf -bt atmega16.bit -o uh atmega16
# commentez ligne suivante si pas ADEPT installe
djtgcfg prog -d Nexys3 --index 0 --file atmega16_rp.bit
Quelques précisions sur ce script :
- vous devez adapter le PATH à votre installation de l'ISE et de votre compilateur C pour l'AVR.
- memoryS6.bmm est lié au fait que l’on utilise maintenant une carte avec un spartan6 (lire documentation data2mem dans ce livre)
- la dernière ligne sert à programmer la carte Nexys3 en ligne de commande avec l'outil ADEPT de Digilent
Ressource disponible
[modifier | modifier le wikicode]Nous mettons à disposition un fichier contenant l’ensemble de l'ATMega16. Il a été testé sur une carte digilent Nexys 3 possédant un FPGA spartan 6 avec une horloge à 100 MHz. Nous avons gardé la division par deux des versions précédentes : il fonctionne donc à 50 MHz. Vous pouvez le faire fonctionner juste en changeant le fichier ".ucf" sur les cartes spartan3, spartan3E et Basys2 mais à 25 MHz seulement. Gardez alors les contraintes de localisation des mémoires et le fichier memory.bmm que l’on trouve dans le chapitre Programmer in situ ...
- Téléchargez la ressource ATMega16. Les modifications futures concerneront le fichier "io.vhd". Nous mettrons dans ce fichier zip diverses versions de ce fichier "io.vhd". Si vous voulez réaliser un ATMega16 fonctionnel il faut en choisir un seul parmi ceux que vous trouverez.
- Pour le moment il y a un fichier "io_timer.vhd" qui implante un pseudo-timer (voir plus bas dans ce chapitre) avec une interruption de débordement.
Comment donner un nom à un registre de périphérique
[modifier | modifier le wikicode]Pour comprendre ce qui va suivre, rappelez-vous que notre processeur n'a que très peu de périphérique à comparer à l'ATMega16 du commerce. Cela veut dire qu'une bonne partie des registres utilisés pour configurer et utiliser ces périphériques est libre. Ainsi deux solutions s'offrent à vous lorsque vous réalisez des périphériques :
- vous êtes conservateur (ce que nous avons été jusqu'ici) et vous gardez les noms des registres originaux de l'ATMega16.
- vous êtes révolutionnaire et vous ne voyez aucune raison pour garder les noms des registres originaux.
Examinons donc les tâches à accomplir dans les deux cas.
Garder les noms de registres de l'ATMega16
[modifier | modifier le wikicode]Dans ce cas il vous faudra lire les documentations officielles de l'ATMega16 pour connaître le nom et les adresses des registres. Alors vous pourrez automatiquement utiliser ce nom en langage C (ou autre). C'est un avantage mais vous vous trouvez alors dans la situation où vous serez amené à écrire du code C peu lisible. Voici un exemple provenant du projet pacman (et ATMega8) :
//**********************************************************************************************************
// function setenemyXY()
// purpose: put the enemy with x and y coordinates
// arguments:
// corresponding x and y coordinates
// return:
// note:
//**********************************************************************************************************
void setenemyXY(uint8_t x,unsigned char y){
// voir fichier io2.vhd pour comprendre
DDRA=x;
DDRC=y;
}
Et la question subsidiaire : quel est le rapport entre DDRA et DDRC et le pacman ? Notez qu'en plus DDRA ne fait pas partie des PORTs de l'ATMega8 du commerce.
En résumé, il nous a fallu lire la documentation officielle du processeur pour connaître nom et adresses des registres. Aucun changement dans les programmes C mais un code peu lisible pour un lecteur non averti.
Changer les noms de registre de l'ATMega16
[modifier | modifier le wikicode]L'autre technique consiste à ne pas garder le nom des registres. Le problème est que maintenant le compilateur C ne connaîtra pas vos noms. L'avantage est que cela vous évite de lire la documentation du processeur et l'inconvénient que cela vous oblige à gérer à la main vos noms de registres. Ceci est réalisé de manière simple :
/* Port A */
// Les ports ci-dessous n'existent pas dans l'ATMega16 de la vraie vie mais nous les avons ajouté dans notre cœur
// d'où leur présence ici pour ne pas modifier les fichiers d'entête !
//#define PINA _SFR_IO8(0x19)
#define DDRA _SFR_IO8(0x1A)
#define PORTA _SFR_IO8(0x1B)
//#define PORTA _SFR_MEM8(0x3B)
qui montre comment ont été ajoutés des registres inexistants dans ce cas, à savoir, DDRA et PORTA.
Ne perdez pas de vue non plus que la définition "#define DDRA _SFR_IO8(0x1A)" définit l'adresse du registre DDRA en 0x3A et non pas en 0x1A comme suggéré dans l'instruction. Il y a donc un décalage de 0x20 entre les deux adresses. Cela veut dire que vous utiliserez 0x3A dans le VHDL mais 0x1A dans le C ! Ce sera toujours comme cela si vous utilisez "_SFR_IO8". Si vous voulez une correspondance parfaite, vous pouvez utiliser "_SFR_MEM8" mais nous ne savons pas si cette macro est toujours disponible.
Des constantes pour simplifier l'écriture des adresses des registres
[modifier | modifier le wikicode]Nous n'avons pas utilisé la technique des constantes jusqu'à présent. Il est possible dans les "case" des process "io_rd" et "io_wr" d’utiliser directement les adresses des registres définies par leur nom si elles sont déclarées comme constantes comme ci-dessous.
-- constantes pour egistres des périphériques de l'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";
Voila alors à quoi pourrait ressembler un process "io_wr" :
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 DDRD => Balle_xLow <= I_DIN; --DDRD
when PORTD => Balle_xHigh <= I_DIN; --PORTD
when DDRB => Balle_yLow <= I_DIN; --DDRB
when PORTB => Balle_yHigh <= I_DIN; --PORTB
-- when DDRC => raqD_y <= I_DIN; --DDRC
when PORTC => raq_x <= I_DIN; --PORTC
when PORTA => s_ligne1 <= I_DIN; -- PORTA
when DDRA => s_ligne2 <= I_DIN; -- DDRA
-- gestion écriture RAM avec registres EEPROM en fait
when EEDR => s_DIB <= I_DIN; -- EEDR : EEPROM Data Register
when EEARL => s_ADDRB <= I_DIN; --EEARL
when EEARH => s_ENB <= I_DIN(7);s_WEB<=I_DIN(6); --EEARH
-- gestion UART
when UCSRC => -- handled by uart
when X"41" => -- handled by uart
when X"43" => L_RX_INT_ENABLED <= I_DIN(0);
L_TX_INT_ENABLED <= I_DIN(1);
when others =>
end case;
end if;
end if;
end process;
où l’on voit apparaître les constantes, ce qui rend le programme un peu plus lisible. Notez que cette façon de faire impose de ne pas appeler les sorties avec ces noms. On pourra garder la notation allemande de l'auteur : préfixer par "I_" pour une entrée et par "Q_" pour une sortie.
Malgré notre connaissance de l'allemand, nous ne savons pas à quoi correspond le "Q" qui semble avoir été imposé pour la norme 1131 de l'automatisme.
Certains lecteurs se demandent pourquoi avoir laissé X"41" et X"43" dans le case. Nous, nous nous demandons pourquoi sont-ils là ? Pour votre information les registres 0x41 et 0x43 ne concernent pas la rs232. Par exemple 0x43 est un registre qui s’appelle OCR2 et concerne le timer 2 ! Nous essaierons de les faire disparaître quand nous retravaillerons sur cette liaison série pour voir.
Nos conventions sur les registres de périphérique dans ce chapitre
[modifier | modifier le wikicode]Notez avant de continuer que le nom des périphériques est choisi lors de la programmation en C et non lors de l'implantation matérielle. Ce qui est choisi lors de l'implantation matérielle est une adresse libre. Ce qui est choisi en C c’est comment vous appelez cette adresse.
Nous avons décidé de garder partiellement la première méthode et d'évoluer vers la deuxième.
- Les registres utilisés par l'auteur du cœur original gérant la rs232 seront gardés. Il s'agit de UCSRA, UCSRB et UDR.
- Les registres appelés PORTs seront gardés à une condition, c’est qu’ils écrivent/lisent directement leur données sur les broches du FPGA sans aucun traitement matériel. Contrairement aux processeurs du commerce, les PORTs ne seront pas implantés de manière bidirectionnelles. Nous distinguerons donc une écriture dans PORTB d'une lecture de PINB.
- Les nouveaux registres seront appelés en C avec des noms évocateurs.
Des bonnes résolutions pas tenues
[modifier | modifier le wikicode]Nous avons eu l’occasion de développer un tas de périphériques depuis l'écriture du principe de la section précédente... et n'avons pas tenu ces promesses de clarification. Nous allons donc chercher à donner des conseils aux utilisateurs mieux organisés que nous...
Bon, reprenons dans le désordre. Ce que nous faisons dans ce livre s’appelle du co-design en anglais. Il s'agit donc de développer du matériel et de le programmer par du logiciel.
En général les concepteurs hardware et software ne sont pas les mêmes. Ce qui est fait ici est donc exceptionnel (dans le sens pas très général). Regardez par exemple le chapitre sur le microBlaze si vous voulez comprendre. Dans ce processeur il n'y a pas de registres de périphériques, mais seulement un espace d'adressage pour les mettre. C'est ainsi que cela se passe toujours si vous prenez un processeur conçu par les fondeurs (microBlaze pour Xilinx, NIOS pour Altera). Mais pour faire cela, Xilinx, Altera et d'autres ont inventé un mécanisme automatique (ou semi-automatique) pour lier le matériel et le logiciel.
Il est cependant possible, pour vous aussi, de considérer l'espace des registres de périphériques comme un espace vierge à remplir par vos propres périphériques. Même pour l'ATMega16 que l'on utilise. Comme le concepteur du matériel et du logiciel est le même vous avez le choix entre plusieurs options :
- adapter les noms de registres à vos applications et donner le même nom aux deux
- en C cela se fait en changeant le fichier iom16.h, ce que l'on déconseille ou en redéfinissant vos propres noms avec des "_SFR_IO8" dans le programme principal ou dans un fichier d'entête spécifique ce que l'on conseille.
- en VHDL cela se fait avec des définitions des constantes comme dans la précédente section
- garder les noms des registres que le compilateur C connaît et les utiliser aussi en VHDL
- garder les noms pour le C et prendre les adresses (et non des noms) pour les registres
Le dernier point est ce que l'on a utilisé au chapitre précédent, en tant que débutant, et ne nous a pas gêné. Il n'empêche que nous le déconseillons pour l'enseignement.
Le deuxième est plutôt ce que l'on utilise de manière classique maintenant.
Le premier point est pour nous l'idéal pédagogique mais demande un peu plus d'organisation (que nous en avons nous-même).
Réalisation des périphériques
[modifier | modifier le wikicode]Un certain nombre de périphériques a déjà été implémenté dans le chapitre précédent. Nous allons maintenant les examiner de façon plus systématique et résoudre ainsi tous les problèmes que vous pourrez rencontrer dans votre vie d'architecte en périphérique.
Comment interfacer un registre en écriture ?
[modifier | modifier le wikicode]Définir des PORTs en sortie se fait dans le process iowr du fichier io.vhd dont voici la version originale :
iowr: process(I_CLK)
begin
if (rising_edge(I_CLK)) then
if (I_CLR = '1') then
L_RX_INT_ENABLED <= '0';
L_TX_INT_ENABLED <= '0';
elsif (I_WE_IO = '1') then
case I_ADR_IO is
when X"38" => -- PORTB
Q_PORTB <= I_DIN;
when X"35" => -- PORTC
Q_PORTC <= I_DIN;
when X"32" => -- PORTD
Q_PORTD <= I_DIN;
when X"2A" => -- UCSRB
L_RX_INT_ENABLED <= I_DIN(7);
L_TX_INT_ENABLED <= I_DIN(6);
when X"2B" => -- UCSRA: handled by uart
when X"2C" => -- UDR: handled by uart
when X"40" => -- UCSRC/UBRRH: (ignored)
when others =>
end case;
end if;
end if;
end process;
Vous voyez apparaître une correspondance entre des PORTs (en commentaire) et des numéros. Le nom des PORTs et les numéros correspondants proviennent de la documentation officielle du processeur. Comme déjà expliqué, on n’est pas obligé de le respecter mais le faire évite de changer le fichier avr/io.h connu du compilateur.
Vous constatez donc que le cœur de départ gère un certain nombre de PORTs : PORTB, PORTC, PORTD et d'autres registres qui ne nous intéressent pas pour le moment. Nous présentons l’ensemble de ce qui vient d’être dit sur un schéma ci-dessous. Tous les registres et PORTs ne sont pas dessinés et seul le processus "iowr" est montré . La terminologie utilisée dans les micro-contrôleurs sépare la notion de PORT et la notion de registre. Un port est un registre dont les valeurs sortent vers l'extérieur, sur des broches. Ce n’est pas le cas des registres. Nous ne suivrons pas cette terminologie ici de manière rigoureuse.
Pour information I_CLK est relié à I_CLK_100 divisée par deux. Nous avons en effet rebaptisé l'horloge car nous utilisons maintenant une carte spartan6 disposant de 100 MHz. Pour les cartes avec horloge à 50 MHz vous pouvez rebaptiser l'horloge ou pas. Les dessins resteront avec l'horloge à 50 MHz, comme celui qui est ci-dessous. Une autre façon de dire les choses est que chaque fois que vous voyez "I_CLK_50" dans un dessin cela désigne l'horloge du processeur (qui peut donc être à 100 MHz) |
Nous présentons maintenant un schéma où seul le processus "iowr: process(I_CLK)" permettant une sortie dans un PORT ou registre est dessiné : son objectif est de dispatcher "I_DIN[7:0]" dans les différents registres en fonction de la valeur de "I_ADR_IO".
Pour revenir un peu sur la façon utilisée pour nommer les registres, notez ici la présence des noms de PORTs. En effet ceux-ci sont directement reliés aux sorties "Q_PORTB", "Q_PORTC" et "Q_PORTD" du processeur sans aucun traitement préalable. Il est donc normal de les appeler PORT.
Comment interfacer un registre en lecture ?
[modifier | modifier le wikicode]Les PORTs et registres en entrée se gèrent dans le process iord :
iord: process(I_ADR_IO, I_PINB,
U_RX_DATA, U_RX_READY, L_RX_INT_ENABLED,
U_TX_BUSY, L_TX_INT_ENABLED)
begin
-- addresses for mega16 device (use iom16.h or #define __AVR_ATmega16__).
--
case I_ADR_IO is
when X"2A" => Q_DOUT <= -- UCSRB:
L_RX_INT_ENABLED -- Rx complete int enabled.
& L_TX_INT_ENABLED -- Tx complete int enabled.
& L_TX_INT_ENABLED -- Tx empty int enabled.
& '1' -- Rx enabled
& '1' -- Tx enabled
& '0' -- 8 bits/char
& '0' -- Rx bit 8
& '0'; -- Tx bit 8
when X"2B" => Q_DOUT <= -- UCSRA:
U_RX_READY -- Rx complete
& not U_TX_BUSY -- Tx complete
& not U_TX_BUSY -- Tx ready
& '0' -- frame error
& '0' -- data overrun
& '0' -- parity error
& '0' -- double dpeed
& '0'; -- multiproc mode
when X"2C" => Q_DOUT <= U_RX_DATA; -- UDR
when X"40" => Q_DOUT <= -- UCSRC
'1' -- URSEL
& '0' -- asynchronous
& "00" -- no parity
& '1' -- two stop bits
& "11" -- 8 bits/char
& '0'; -- rising clock edge
when X"36" => Q_DOUT <= I_PINB; -- PINB
when others => Q_DOUT <= X"AA";
end case;
end process;
Vous pouvez voir que certains registres sont implantés bit par bit et d'autres en entier.
Nous avons présenté sous forme de programme VHDL le process "iord" mais nous allons en faire un schéma de principe. Comme d'habitude, prenez du temps devant ce schéma pour bien comprendre ce que l’on cherche à présenter.
Son principe est de former le signal "Q_DOUT[7:0]" à partir d'une adresse "I_ADR_IO[7:0]" et de données provenant de l'extérieur ou pas. Il est possible que vous soyez surpris par le sens de "Q_DOUT[7:0]" comme sortie du processus. Mais cette sortie devient une entrée du processeur, non ?
Si vous comparez la figure avec le code (faites-le vraiment) vous vous apercevez que la figure est moins complète que le code : tous les registres ne sont pas représentés. Ce qui est important, c’est le principe : voir la relation entre le "case when" du VHDL et le multiplexeur du dessin.
Comment interfacer un registre à la fois en lecture et écriture ?
[modifier | modifier le wikicode]Nous cherchons maintenant à implémenter des périphériques classiques, nous voulons dire du genre de ceux que l’on trouve habituellement dans un vrai processeur. Prenons le cas du timer0 dans un ATMega16. Il nous donne accès à un registre appelé TCNT0 qui peut fonctionner tout seul quand le timer0 est en route, mais dans lequel on peut aussi écrire, ou lire. Comment faire cohabiter donc toutes ces possibilités ?
Nous ferons probablement plus tard un exemple avec une entité et une architecture, mais pour le moment vous pouvez lire la section Réaliser un périphérique avec interruption pour vous en faire une idée.
Une bonne idée, par exemple, est d'interfacer les PORTs en sortie et en entrée. Nous ne l'avons jamais fait mais cela permettrait par exemple d'écrire :
// incrémentation du PORTB
PORTB++;
On rappelle en effet que PORTB++ est équivalent à PORTB = PORTB+1
Le fait que PORTB se trouve à droite de l'affectation veut dire qu’il faut le considérer comme une entrée. Le fait qu’il apparaisse à gauche veut dire qu’il est considéré comme une sortie.
Des erreurs subtiles peuvent apparaître si votre compilateur utilise les instructions SBI et CBI à votre insu. Cela ne nous est jamais arrivé malgré les milliers de lignes qui ont tournées dans ce SOC ATMega16. Pourtant nous avons été amené à tester ces deux instructions directement en assembleur et constater leur dysfonctionnement. Ce dysfonctionnement n'est pas lié au cœur VHDL mais au fait que si vous voulez faire fonctionner
SBI PORTC,0
Il vous faut boucler la sortie PORTB sur l'entrée PORTB
--***** Regardez le PORTC dans le process d'écriture *****
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; -- on sort vers l'extérieur ici
S_PORTC <= I_DIN; -- on sort vers un signal interne ici
--***** le process d'écriture continue bien sûr
--***** Regardez le PORTC dans le process de lecture *****
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
-- ................... on a coupé ici ..........
when PORTC => Q_DOUT <= S_PORTC; -- le signal de sortie est disponible en entrée
La remarque précédente impose donc le principe suivant :
Pour que les instructions CBI et SBI fonctionnent normalement sur un registre XXXX, il faut que ce registre apparaisse à la fois dans le process d'écriture et dans le process de lecture.
Comment interfacer un FIFO en écriture ?
[modifier | modifier le wikicode]Nous donnerons un exemple simple plus tard. Pour le moment, notez que ceci a déjà été fait dans un autre projet en deux étapes :
- connecter l'entrée du FIFO à I_DIN
- réaliser le signal d'écriture du FIFO : L_WE_UART2 <= I_WE_IO when (I_ADR_IO = X"24") else '0'; -- write UART2 ADCL
Le résultat présenté ici peut être généralisé à tout registre. Nous voulons parler d'un périphérique qui a déjà son registre d'entrée.
Dans votre quête de périphériques vous vous trouverez dans quatre situations :
- votre périphérique est combinatoire et a besoin d'un registre pour être commandé. Il (le registre) sera réalisé dans le process "iowr" car le case est dans un "if(rising_edge(clk))". Cette façon de procéder crée donc automatiquement un registre.
- vous récupérez votre périphérique sur internet ou ailleurs et il possède déjà son propre registre (comme le FIFO) et une entrée de validation d'écriture. Dans ce cas vous connectez directement votre entrée de validation en dehors du case, avec un when, comme cela est fait avec le signal d'écriture du FIFO. Vous connectez l'entrée du registre directement à I_DIN comme pour le FIFO.
- votre périphérique possède bien un registre en entrée mais sans entrée de validation d'écriture. Cela nous semble anormal mais nous l'avons déjà fait. Procéder alors comme si votre périphérique était combinatoire. Votre écriture dans votre périphérique aura lieu avec une période d'horloge de retard, et c'est le seul défaut.
- votre périphérique peut être un mélange des deux premiers points. Vous avez à la fois à gérer une valeur qui va être mise dans un registre (donc dans le case) et une opération validation ponctuelle. C'est ce qui se passe dans la section "Comment gérer un compteur 32 bits" un peu plus loin.
Comment interfacer un FIFO en lecture ?
[modifier | modifier le wikicode]Puisque nous nous intéressons à un FIFO en lecture, cela laisse entendre que c’est un périphérique (un bout de VHDL en tout cas) qui viendra écrire dedans. Mais notre processeur qui viendra le lire. Ceci n’est pas sans conséquence :
Tout périphérique connecté à un FIFO devra gérer un signal "done" indiquant que sa donnée est prête. Ce "done" permettra l'écriture dans le FIFO.
Dans cette section nous allons nous intéresser seulement à l'interface entre le FIFO et le processeur. Nous laissons tomber les explications sur le "done" et aurons certainement l’occasion de revenir dessus lors d'une implantation pratique.
Quelques informations supplémentaires sur le VHDL correspondant :
-- utilisé dans le registre PINC en lecture
-- b7 b6 b5
signal s_data_present, s_buffer_full, s_half_full : std_logic;
Les signaux ci-dessus sont utilisés dans le process de lecture :
iord: process(I_ADR_IO, I_SWITCH,
U_RX_DATA, U_RX_READY, L_RX_INT_ENABLED,
U_TX_BUSY, L_TX_INT_ENABLED)
begin
-- addresses for mega16 device (use iom16.h or #define __AVR_ATmega16__).
--
case I_ADR_IO is
when X"33" => Q_DOUT <= -- PINC:
s_data_present -- data present ?
& s_buffer_full -- buffer plein ?
& s_half_full -- buffer à moitié plein ?
& '0' -- non utilisé
& '0' -- non utilisé
& '0' -- non utilisé
& '0' -- non utilisé
& '0'; -- non utilisé
when X"30" => Q_DOUT <= PIND_DATA; -- PIND:
Nous n'avons pas mis l’ensemble du process ici mais seulement ce qu’il y a de nouveau pour cet exemple.
Le choix de PINC pour projeter les bits du FIFO et PIND pour les données est absolument arbitraire. Utilisez n’importe quel registre libre si cela vous choque !
Notez par la même occasion que ce choix n’est pas conforme à la politique de dénomination des registres que nous avons décidé d’utiliser un peu plus haut ... puisque ce qui rentre dans PIND et PINC a subi un traitement matériel. Mais le dessin incomplet ne montre pas vraiment ce traitement matériel, c'est-à-dire ce qui nourrit ce FIFO !
Il y a un autre point sur lequel il nous faut revenir : la construction du signal read qui va au FIFO. Il se fait avec le simple code :
--regardez la figure un peu plus loin pour comprendre
L_RD_FIFO <= I_RD_IO when (I_ADR_IO = X"30") else '0'; -- read PIND
Avec ce signal, toute lecture de PIND en C provoquera simultanément une évacuation de la donnée du FIFO : c’est un décalage dans le FIFO grâce à ce signal qui permet de récupérer la donnée suivante la prochaine fois qu'on viendra le lire.
La figure montre qu’il faudra aussi câbler le FIFO, ce qui n’est pas montré dans nos extraits de code VHDL. Mais il faut bien laisser un peu de travail à nos lecteurs.
Pour information, nous avons eu l’occasion d’utiliser cette technique avec un module le lecture de clavier PS/2 qui remplissait le FIFO au fur et à mesure. Cette technique est utilisée aussi dans la gestion de l'accéléromètre de la manette Nunchuk du projet pacman plus bas.
Comment interfacer un compteur 32 bits ?
[modifier | modifier le wikicode]Le titre de cette section n'est probablement pas assez explicite. Soyons donc plus concret. Imaginons que nous devons compter des événements qui peuvent être nombreux et nécessiter donc 32 bits. Puisque nous avons un processeur 8 bits, il nous faudrait en principe 4 ports en sortie et 4 ports en entrées pour gérer ces 32 bits en entrée et sortie. Supposons que nous n'ayons absolument pas besoin de lire les 32 bits car une partie d'affichage (ou autre) s'en occupe. La question est alors : est-il possible par une simple écriture dans un PORT (8 bits) d'incrémenter un compteur 32 bits ? Voila la question à laquelle nous désirons nous atteler maintenant.
La technique consiste à utiliser un compteur avec un signal de validation "en" et de construire ce "en" comme on l'a fait pour le signal "read" de la FIFO.
Un exemple pour comprendre
[modifier | modifier le wikicode]Pour être concret, voici un exemple qui a été donné comme examen final à des étudiants. Il est sur 16 bits mais montre bien les problèmes à résoudre pour y parvenir. Voici le compteur proprement dit :
-- Serge Moutou juin 2013
-- fichier cmptAngle.vhd
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_arith.all;
use IEEE.std_logic_unsigned.all;
ENTITY cmpt_Angle is
PORT(
clk,reset,en : IN std_logic;
inc_dec_i : IN std_logic_vector(7 DOWNTO 0);
-- 3243 < angle_o < CDBD
angle_o : OUT std_logic_vector(15 downto 0));
END cmpt_Angle;
ARCHITECTURE Behavioural OF cmpt_Angle IS
-- =1 si incrementation =0 si decrementation
signal increment : std_logic;
signal s_angle : std_logic_vector(15 downto 0);
BEGIN
PROCESS(clk,reset) BEGIN
IF reset='1' THEN
s_angle <=MAX;
increment <= '1';
ELSIF rising_edge(clk) THEN
IF en = '1' THEN
IF increment = '1' THEN
s_Angle <= s_Angle + inc_dec_i;
IF s_Angle >= MIN and s_Angle(15)='0' THEN
s_angle <= MIN;
increment <= '0';
END IF;
ELSE
s_Angle <= s_Angle - inc_dec_i;
IF s_Angle <= MAX and s_Angle(15)='1' THEN
s_angle <= MAX;
increment <= '1';
END IF;
END IF;
END IF;
END IF;
END PROCESS;
angle_o <= s_angle;
END Behavioural;
Seule l'entité a un intérêt et le signal s_angle qui est sur 16 bits. Le compteur est un peu spécial car destiné à un cœur CORDIC.
Le câblage de ce composant se fait dans io.vhd par :
cmptAngle: cmpt_Angle PORT MAP(
clk => I_CLK,
reset => I_CLR,
en => s_en,
inc_dec_i => s_PORTA,
angle_o(15 downto 8) => Angle_MSB,
angle_o(7 downto 0) => Angle_LSB);
Ce code laisse entendre qu’il se passera quelque chose par une écriture dans PORTA ! Une confirmation ?
iowr: process(I_CLK)
begin
if (rising_edge(I_CLK)) then
if (I_CLR = '1') then
L_RX_INT_ENABLED <= '0';
L_TX_INT_ENABLED <= '0';
elsif (I_WE_IO = '1') then
case I_ADR_IO is
when X"3B" => -- PORTA
s_PORTA <= I_DIN;
when X"38" => -- PORTB
s_PORTB <= I_DIN;
-- plusieurs lignes sont omises
end process;
s_en <= I_WE_IO when (I_ADR_IO = X"3B") else '0'; -- write PORTA
De tout cet ensemble d'extraits de code, il est possible de déduire qu'une écriture d'une valeur X dans PORTA incrémentera le compteur 16 bits de X.
Comment interfacer une mémoire RAM en la projetant dans l'espace mémoire ?
[modifier | modifier le wikicode]Ce problème a déjà été traité dans ce livre lors de l'apparition du projet pacman. Mais nous l'avons alors traité d'une manière qui mérite d’être expliquée de nouveau.
Ce qui est fait ici ne peut que très difficilement être réalisé avec un processeur du commerce. Cette section explique donc exactement en quoi un processeur enfoui dans un FPGA peut être très supérieur à son cousin du monde réel. Atténuons cependant ce qui peut paraitre comme un excès d'enthousiasme. Les domaines d’applications où il est nécessaire d'interfacer une mémoire RAM à un processeur sont très peu nombreux dans les champs d'application des 8 bits. Avec les utilisations graphiques et les processeurs 32 bits, cela doit arriver plus fréquemment cependant.
Comment interfacer une mémoire RAM avec des PORTs ?
[modifier | modifier le wikicode]Au lieu de projeter la RAM directement dans la RAM de l'AVR, il est possible de passer par des Registres/PORTs pour ce travail. Pourquoi ne pas reprendre, par exemple, les registres de l'EEPROM ? Cette technique est cependant plus lente mais probablement plus facile à mettre en œuvre. Il nous a suffit de rajouter un PORT à un BRAM et de la connecter à nos PORTs.
Voici un exemple de programme associé :
//**************************************************************************************************************************
// function putLevel()
// purpose: put level in the screen by writing in ROM
// arguments:
// corresponding level
// return:
// note: WEB est en b6 et ENB en b7 tandis que SSRB est forcé matériellement à 0
//**************************************************************************************************************************
void putLevel(uint8_t level){
uint8_t Data;
Data = level & 0x0F;
EEARH = 0;
// voici l'adresse où l’on écrit
EEARL = 102;
// voici la donnée que l’on écrit
EEDR = Data + '0';
// voici la commande d'écriture
EEARH = 0xC0;
EEARH = 0;
Data = level & 0xF0;
Data >>= 4;
EEARL = 101;
EEDR = Data + '0';
EEARH = 0xC0;
EEARH = 0;
}
Les fins connaisseurs de l'écriture en EEPROM auront remarqué que l’on n'a pas respecté la façon dont cela se passe avec un ATMega. Nous avons utilisé EEARH comme registre de commande alors qu'en principe c’est le registre d'adresse haute ! Cette façon de faire présente un inconvénient : il ne nous est pas possible de déclarer directement des variables dans cette mémoire alors que cela est possible avec un vrai AVR. Voici par exemple un code trouvé dans la documentation officielle de l'ATMega16 où l’on voit que le registre de commande est EECR et que 2 bits sont concernés EEWE et EEMWE.
void EEPROM_write(unsigned int uiAddress, unsigned char ucData)
{
/* Wait for completion of previous write */
while(EECR & (1<<EEWE));
/* Set up address and data registers */
EEAR = uiAddress;
EEDR = ucData;
/* Write logical one to EEMWE */
EECR |= (1<<EEMWE);
/* Start eeprom write by setting EEWE */
EECR |= (1<<EEWE);
}
Nous nous intéresserons à ce problème un peu plus tard. Les changements sont mineurs :
- changer de registre de commande (une adresse à changer)
- trouver un mécanisme pour que le bit EEWE revienne à 0 tout seul en une période d'horloge
- tester : c’est ce qui prend le plus de temps
Voir aussi : Examen final LO11 2015 qui utilise aussi une mémoire RAM accessible par des PORTs sur une architecture ATTiny861 décrite dans la partie TP de ce livre.
Pourquoi ne pas vouloir refaire les périphériques de l'ATMega16 original ?
[modifier | modifier le wikicode]Tout le monde s'accorde pour dire que l’intérêt d'enfouir un processeur dans un FPGA est tout simplement que ses périphériques peuvent être taillés sur mesure dans la partie restante du FPGA.
Quelques exemples peut être ?
- L'ATMega16 que nous proposons dispose d'une liaison série. Elle n’est pas complète puisqu'elle ne gère qu'une vitesse fixe. Toute instruction visant à changer cette vitesse échouera donc. Nous n'avons pas encore rencontré de situation où cela présentait un problème sérieux !
- La liaison i2c est gérée par l'ATMega16 avec un ensemble de registres. Nous avions comme objectif, de copier cette gestion pour gérer la manette Nunchuk (année scolaire 2012/2013) mais avons fini par abandonner cette idée par souci de simplification. Bien sûr cela aura comme conséquence qu'une gestion d'un accéléromètre i2c, par exemple, nécessitera de tout refaire... mais la simplification en valait la peine. Pour information un périphérique i2c plus générique a été réalisé en janvier 2017 un peu plus loin dans ce chapitre.
- Le problème des timer mérite probablement plus d'attention. En effet ces derniers peuvent être utilisés dans les systèmes multitâches. Ne pas les implémenter comme dans le processeur original nécessite donc de lire le code source du noyau multitâche que vous désirez utiliser. Dans ce cas précis, ce qui est gagné sur le matériel (simplification par la description d'un timer maison) est perdu sur le logiciel (nécessité de vérifier que le timer maison est compatible avec le logiciel). Les compétences techniques de l'équipe chargée du projet seront donc capitales pour choisir entre les deux options.
Réaliser un périphérique avec interruption
[modifier | modifier le wikicode]Le problème des interruptions a été évoqué ci-dessus et dans le chapitre sur l'ATMega8. Nous n'avons cependant jamais réalisé de périphérique qui réalise une telle interruption. C'est donc à cette tâche que nous allons nous atteler maintenant.
Essai d'interruption
[modifier | modifier le wikicode]Dans le chapitre précédent nous avons déjà donné un programme qui utilise une interruption en réception rs232. Il vous faut commencer par essayer de le faire fonctionner. Le voici donc rappelé :
#include <avr/io.h>
#include <avr/interrupt.h>
#undef F_CPU
#define F_CPU 100000000UL
#include "util/delay.h"
// interruption de réception
ISR(USART_RXC_vect) // ISR(_VECTOR(11))
{
PORTC = UDR;
}
int main(int argc, char * argv[])
{
UCSRB = (1<<RXEN)|(1<<RXCIE); // pour pouvoir déclencher interruption RS232
sei(); // autorise interruption générale
for (;;);
}
Lorsqu'un hyperterminal est connecté à 38400 bauds, 8 bits de données, sans parité et deux bits de stop, vous devez voir clignoter les leds au gré de vos envois par le clavier. Nous l'avons fait fonctionner à 100 MHz dans la carte Nexys 3 (architecturée autour d'un spartan6) sans problème.
Si ce programme fonctionne c’est parce que Juergen Sauermann, l'auteur du projet, a livré un exemple d'implantation matérielle d'interruption avec la rs232. Il nous faut donc lire ce code et l'adapter à nos besoins.
Un pseudo-timer pour réaliser une interruption
[modifier | modifier le wikicode]Nous avons décidé, comme premier exemple, de réaliser une interruption par débordement d'un compteur/timer. Nous avons appelé cette section pseudo-timer car nous allons implanter un timer mais de manière très simplifiée (avec une pré-division fixe par exemple).
Le timer proprement dit
[modifier | modifier le wikicode]L'idée générale est de faire un compteur sur 20 bits mais avec lequel une écriture provoquera la seule mise à jour des 8 bits de poids fort.
Il suffit d’abord d'ajouter un process de gestion du timer
timer0: process(I_CLK) begin
if (rising_edge(I_CLK)) then
if (I_ADR_IO = X"52" and I_WE_IO = '1') then -- écriture dans TCNT0
pseudotimer(19 downto 12) <= I_DIN;
L_TOV0 <= '0'; --flag remis a 0 pendant écriture
else
pseudotimer <= pseudotimer + 1;
if pseudotimer = x"FFFFF" then
L_TOV0 <= '1'; -- timer 0 overflow
end if;
end if;
end if;
end process;
où pseudotimer est un signal 20 bits à déclarer dont seuls les bits de poids forts nous intéressent vraiment. Le reste est là pour réaliser une division de l'horloge qui fonctionne à 50 MHz. Comme on peut le voir la mise à 0 de l'overflow se fait par une écriture dans le timer. Pour votre information, ce n’est pas comme cela que fonctionne l' ATMega16. Et ce dernier offre la possibilité supplémentaire de mise à 0 de TOV0 par écriture directe dans le registre TIFR. Nous n'avons pas réalisé cette deuxième fonctionnalité par souci de simplification.
Autorisation de l'interruption timer
[modifier | modifier le wikicode]C'est un bit appelé TOIE0 du registre TIMSK (adresse 0x59) qui gère cette autorisation. Si l’on veut le gérer correctement il nous faut donc ajouter une ligne au process "iowr" comme ceci :
iowr: process(I_CLK)
begin
if (rising_edge(I_CLK)) then
if (I_CLR = '1') then
L_RX_INT_ENABLED <= '0';
L_TX_INT_ENABLED <= '0';
elsif (I_WE_IO = '1') then
case I_ADR_IO is
when X"38" => -- PORTB
Q_7_SEGMENT <= I_DIN(6 downto 0);
when X"35" => -- PORTC
Q_LEDS <= I_DIN;
when X"32" => -- PORTD
Q_AN <= I_DIN(3 downto 0);
when X"2A" => -- UCSRB
L_RX_INT_ENABLED <= I_DIN(7);
L_TX_INT_ENABLED <= I_DIN(6);
when X"2B" => -- UCSRA: handled by uart
when X"2C" => -- UDR: handled by uart
when X"40" => -- UCSRC/UBRRH: (ignored)
when X"59" => -- TIMSK ajouté pour nos besoins
L_TOIE0 <= I_DIN(0);
when others =>
end case;
end if;
end if;
end process;
Voila, nous sommes prêt, il ne reste plus qu’à modifier le process "ioint".
Vous ne voyez pas apparaître la ligne "when x"52" => --TCNT0" puiqu'elle est déjà gérée dans le process "timer0" de la section précédente.
Une lecture possible du timer
[modifier | modifier le wikicode]Il est possible de lire la valeur des 8 bits de poids forts de TCNT0 avec le process de lecture :
-- IO read process
--
iord: process(I_ADR_IO, I_SWITCH,
U_RX_DATA, U_RX_READY, L_RX_INT_ENABLED,
U_TX_BUSY, L_TX_INT_ENABLED)
begin
-- addresses for mega8 device (use iom8.h or #define __AVR_ATmega8__).
--
case I_ADR_IO is
when X"2A" => Q_DOUT <= -- UCSRB:
L_RX_INT_ENABLED -- Rx complete int enabled.
& L_TX_INT_ENABLED -- Tx complete int enabled.
& L_TX_INT_ENABLED -- Tx empty int enabled.
& '1' -- Rx enabled
& '1' -- Tx enabled
& '0' -- 8 bits/char
& '0' -- Rx bit 8
& '0'; -- Tx bit 8
when X"2B" => Q_DOUT <= -- UCSRA:
U_RX_READY -- Rx complete
& not U_TX_BUSY -- Tx complete
& not U_TX_BUSY -- Tx ready
& '0' -- frame error
& '0' -- data overrun
& '0' -- parity error
& '0' -- double dpeed
& '0'; -- multiproc mode
when X"2C" => Q_DOUT <= U_RX_DATA; -- UDR
when X"40" => Q_DOUT <= -- UCSRC
'1' -- URSEL
& '0' -- asynchronous
& "00" -- no parity
& '1' -- two stop bits
& "11" -- 8 bits/char
& '0'; -- rising clock edge
when X"36" => Q_DOUT <= I_SWITCH; -- PINB
when X"52" => Q_DOUT <= pseudotimer(19 downto 12); --TCNT0
when others => Q_DOUT <= X"AA";
end case;
end process;
Process d'interruption
[modifier | modifier le wikicode]Avant toute chose, il faut connaître le numéro du vecteur d'interruption que nous tentons de réaliser. Cela peut se trouver dans la documentation officielle du micro-contrôleur ou dans le fichier "iom16.h" (/usr/lib/avr/include/avr/iom16.h avec un Linux Ubuntu) du compilateur avr-gcc. On touvre dans ce fichier (parmi bien d'autres choses) :
/* Timer/Counter0 Overflow */
#define TIMER0_OVF_vect_num 9
#define TIMER0_OVF_vect _VECTOR(9)
#define SIG_OVERFLOW0 _VECTOR(9)
qui nous montre que le vecteur que l’on tente de réaliser porte le numéro 9.
Voici donc comment on a modifié le process "ioint" (intéressez-vous à ce qui concerne le vecteur 9) :
ioint: process(I_CLK)
begin
if (rising_edge(I_CLK)) then
if (I_CLR = '1') then
L_INTVEC <= "000000";
else
case L_INTVEC is
-- vector 9
when "101001" =>
if (L_TOIE0 and L_TOV0) = '0' then
L_INTVEC <= "000000";
end if;
-- vector 11 ??
when "101011" => -- vector 11 interrupt pending.
if (L_RX_INT_ENABLED and U_RX_READY) = '0' then
L_INTVEC <= "000000";
end if;
-- vector 12 ??
when "101100" => -- vector 12 interrupt pending.
if (L_TX_INT_ENABLED and not U_TX_BUSY) = '0' then
L_INTVEC <= "000000";
end if;
when others =>
-- no interrupt is pending.
-- We accept a new interrupt.
--
if (L_RX_INT_ENABLED and U_RX_READY) = '1' then
L_INTVEC <= "101011"; -- _VECTOR(11)
elsif (L_TX_INT_ENABLED and not U_TX_BUSY) = '1' then
L_INTVEC <= "101100"; -- _VECTOR(12)
elsif (L_TOIE0 and L_TOV0) = '1' then
L_INTVEC <= "101001"; -- _VECTOR(9)
else
L_INTVEC <= "000000"; -- no interrupt
end if;
end case;
end if;
end if;
end process;
Ce code associé au programme c suivant fonctionne parfaitement :
#include <avr/io.h>
#include <avr/interrupt.h>
#undef F_CPU
#define F_CPU 50000000UL
/* Ce programme fonctionne avec les modifications présentées plus haut */
// interruption de débordement de timer
char deb,vPORTC;
ISR(TIMER0_OVF_vect) // ISR(_VECTOR(9))
{
deb++;
if (deb==100) {
deb = 0;
vPORTC = vPORTC ^ 0xFF;
PORTC = vPORTC;
}
TCNT0 = 0; // remet TOV0 à 0 !!!!
}
int main(int argc, char * argv[])
{
vPORTC = 0xFF;
deb = 0;
TCNT0 = 0;
TIMSK = 0x01; // TOVIE0
sei(); // autorise interruption générale
for (;;);
}
Pouvez-vous calculer à quelle fréquence les LEDs s'allument et s'éteignent ?
Réponse : Si la fréquence est de 100 MHz et donc la période correspondante est de 10 ns. Le débordement du timer a lieu tous les 1024x1024x10 ns soit : 10485760 ns ce qui correspond à une fréquence de 95 Hz. Si vous regardez attentivement le code de l'interruption, vous verrez qu'elle compte de 0 à 100 et seulement à ce moment là, bascule le PORTC. Il faut donc compter 200 fois pour une période soit 2,097152000 s et donc une fréquence de 0,477 Hz.
Depuis la rédaction de cet exercice nous avons été obligé de diviser la fréquence d'horloge par 2 (à 50 MHz) et donc avons multiplié les périodes par 2 par rapport à ce calcul.
Le fonctionnement du process ioint pour l'interruption ajoutée est dangereux et mérite d’être exploré plus en avant ! Il faut que d'une manière ou d'une autre le vecteur d'interruption disparaisse. Ceci ne peut être réalisé qu'avec TOV0. Mais ...
Le fonctionnement du drapeau TOV0 n’est pas conforme à l'ATMegaXX original. Ici, il est mis à 0 par une écriture dans le timer alors que le microcontrôleur vendu par ATMEL le met à 0 en entrée de l'interruption. Nous avons décidé avec Juergen Sauerman de réaliser une version plus proche de la réalité que celle présentée ici. Pour éviter de rédiger une section assez longue nous la donnons comme deuxième ressource dans la section suivante.
Ressources
[modifier | modifier le wikicode]Téléchargez la ressource ATMega16. Le fichier "io_timer.vhd" implante le pseudo-timer de cette section "Réaliser un périphérique avec interruption" avec une interruption de débordement. Rappelons que ce fichier remplacera "io.vhd" dans le projet complet (ne pas mettre les deux fichiers dans un même projet même si les deux vous sont fournis !)
Mais le fonctionnement du flag TOV0 dans cette ressource ne correspond pas à ce qui se passe dans un vrai ATMega16. Nous allons donc ajouter quelques explications et la ressource correspondante.
Correspondance avec Juergen Sauermann et nouvelle ressource
[modifier | modifier le wikicode]Pour expliquer les changements réalisés, nous allons traduire la correspondance que nous avons eu avec Juergen Sauermann, l'auteur de ce cœur.
> Le vendredi 3 juillet 2015 Juergen Sauermann nous a répondu
Je vois. La phrase correspondante dans le manuel de l'AVR est probablement celle-ci :
THE OCF0A FLAG IS AUTOMATICALLY cleared when the interrupt is executed. Alternatively, the OCF0A flag can be cleared by softwar BY WRITING A LOGICAL ONE TO ITS I/O BIT LOCATION.
Ainsi la méthode dont je parlais était la seconde (WRITING A LOGICAL ONE TO ITS I/O BIT LOCATION.) tandis que toi tu te referais à la première (THE OCF0A FLAG IS AUTOMATICALLY CLEARED WHEN THE INTERRUPT IS EXECUTED).
Cela n'a pas été inventé par AVR mais déjà connu comme acquittement (ou INT_ACK) dans les Motorola 68020 et (peut-être dans des processeurs encore plus anciens). La bonne place pour générer INT_ACK est dans OPC_DECO.VHD, mais pas dans RETI. Plutôt dans le pseudo opcode d'interruption. Et cela parce que INT_ACK doit être généré au début de l'interruption et non pas à la fin.
Ajouter INT_ACK dans OPC_DECO.VHD est relativement simple:
1. Add an output INT_ACK to entity opc_deco
entity opc_deco is
...
Q_INT_ACK : out std_logic;
...
2. mettre INT_ACK à 0 par defaut (autour de la ligne 72):
ALU <= ALU_NOP;
DDDDD <= Rd;
RRRRR <= Rr;
IMM <= X"0000";
Q_INT_ACK <= '0';
PC_OP <= PC_NEXT;
3. Mettre INT_ACK à 1 quant l'opcode est exécuté (LIGNE 95):
when "00" =>
--
-- 0000 0000 0000 0000 - NOP
-- 0000 0000 001v vvvv - INTERRUPT
--
if (I_OPC(5)) = '1' then -- interrupt
Q_ALU_OP <= ALU_INTR;
Q_AMOD <= AMOD_ddSP;
Q_JADR <= "0000000000" & I_OPC(4 downto 0) & "0";
Q_PC_OP <= PC_LD_I;
Q_WE_F <= '1';
Q_WE_M <= "11";
Q_INT_ACK <= '1'; -- ACKNOWLEDGE THE INTERRUPT
end if;
Cela met Q_INT_ACK à 1 pour un cycle et peut être utilisé pour mettre la source d'interruption à 0. En théorie on pourrait créer un INT_ACK par source possible d'interruption au lieu d'un seul pour toutes les interruptions mais comme la majeure partie des interruptions ne sont pas automatiques, je ne ferai pas cela ici mais plutôt dans le process IOINT.
Tu peux maintenant connecter INT_ACK au process IOINT qui doit alors vérifier l'interruption courante pour générer le bon signal INT_ACK par source.
/// Jürgen
Notez que nous ne sommes pas sûr que Juergen nous tutoie, avec l'Anglais on ne sait jamais... Nous avons fait les modifications demandées.
Pour bien comprendre ce mail, il faut avoir à l'esprit qu'une interruption est réalisée par une insertion d'un opcode spécifique qui va mettre le drapeau d'interruption général (appelé I) à 0. Et ceci pour éviter toute nouvelle interruption pendant que l’on en exécute une. Ce drapeau I sera automatiquement repositionné à 1 par l'instruction RETI. Tout ce mécanisme est dans le cœur depuis le début... la seule chose qui a changé c’est l'apparition d'une nouvelle possibilité de mise à 0 d'un drapeau de manière automatique.
Nous publions dans cette section seulement le nouveau pseudotimer pour montrer qu’il est un peu plus complexe maintenant.
timer0: process(I_CLK) begin
if (rising_edge(I_CLK)) then
if (I_ADR_IO = X"52" and I_WE_IO = '1') then
pseudotimer(19 downto 12) <= I_DIN;
-- L_TOV0 <= '0';
else
pseudotimer <= pseudotimer + 1;
end if;
end if;
end process;
TOV0:process(I_CLK) begin
if (rising_edge(I_CLK)) then
if pseudotimer = x"FFFFF" then
L_TOV0 <= '1';
-- automatic reset of TOV0 :
elsif I_INT_ACK ='1' and L_INTVEC = "101001" then
L_TOV0 <= '0';
-- reset of TOV0 with TIFR |= (1<<TOV0); :
elsif I_ADR_IO = X"58" and I_DIN(0)='1' and I_WE_IO = '1' then
L_TOV0 <= '0';
end if;
end if;
end process;
Nous avions promis en début de chapitre d'arrêter avec les versions différentes mais voilà, on ne fait pas toujours comme on veut. Voici donc une version qui permet la mise à 0 du bit TOV0 exactement comme dans celui que vous achèteriez : ATMega16 avec reset automatique du TOV0. Cette nouvelle ressource a été ajoutée en Juillet 2015.
Projet pacman avec l'ATMega16 (2013/2014)
[modifier | modifier le wikicode]Le projet Pacman de l'année précédente avec l'ATMega8 était un programme C de plus de 700 lignes. On s'approchait alors des limites de l'ATMega8. Le fait d’utiliser un ATMega16 maintenant va nous donner une bouée d'oxygène pour la taille du code (16 ko de mémoire programme contre 8 ko).
Lire aussi
[modifier | modifier le wikicode]- Projet pacman pour 2012/2013 dans ce livre
- Étude de la manette Nunchuk aussi dans ce livre
Présentons maintenant les objectifs du projets.
Objectifs
[modifier | modifier le wikicode]Il s'agit de gérer un jeu de pacman à partir d'une partie matérielle donnée. Cependant, contrairement à l'année précédente, la partie matérielle donnée n’est pas complète comme le montre la section suivante. Quant à la partie logicielle, il sera assez difficile aux étudiants de reprendre le travail de l'année précédente. L'ajout d'un ennemi change fortement la donne.
Contrairement à l'année précédente nous allons utiliser la carte platine d'évaluation spartan3e starter kit.
Matériel : gestion de l'accéléromètre de la manette Nunchuk
[modifier | modifier le wikicode]Rappelons ce qui a été fait jusqu'à présent. Nous avons commencé par utiliser un vieux Joystick avec 4 contacts pour haut, bas, gauche et droite. Nous avons remplacé ce périphérique par une manette Nunchuk mais avec comme souci de ne pas changer le programme C de gestion des directions de déplacement. Donc le périphérique développé l'année précédente lisait les valeurs analogiques retournées par Nunchuk et à partir d'un certain seuil positionnait un bit par direction. Il se comportait donc comme le joystick du départ... mais tout cela est peu adapté à la gestion des accéléromètres.
Vous allez partir de la version de l'année précédente avec une gestion du joystick analogique de la manette Nunchuk et lui ajouter une gestion des accéléromètres tout en retirant les comparaisons et les seuils associés. L'ensemble des données de la Nunchuk sera systématiquement stocké dans un FIFO lisible depuis le processeur. Fini donc la gestion matérielle pour remplacer le joystick analogique par un joystick numérique ! C'est notre processeur qui devra tout gérer !
Un module capable de gérer TOUTES les données de la Nunchuk est disponible dans un autre chapitre. C'est lui que vous allez utiliser et interfacer dans "io2.vhd". Vous utiliserez deux registres réservés à l’i2c dans l'ATMega16 :
- TWDR en adresse 0x23 pour les données
- TWCR en adresse 0x56 pour les commandes et états commande TWEN est en bit b2 (écriture) et l'état TWINT = data présent en bit b7 (lecture)
Voici un condensé sous forme de schéma de ce qu’il y a à faire :
Même si cette figure ne représente pas tous les registres et PORTs que nous utilisons dans le projet pacman complet elle nous montre l'essentiel. L'ensemble du travail consiste donc à câbler et ajouter des lignes dans les deux "case" des deux process de lecture et d'écriture. Ce qui est peut être un peu délicat à comprendre est la réalisation du signal "s_readFIFO" tout en haut de la figure. Ce signal est là pour vider le FIFO au fur et à mesure de ses lectures par le processeur. Il sera relié à l'entrée readFIFO du module NunchukTop.
Pour information, votre point de départ est le projet de l'année précédente qui peut se résumer comme présenté dans la figure ci-dessous :
Logiciel : lecture du FIFO pour les données Nunchuk
[modifier | modifier le wikicode]Nous allons vous donner deux sous-programmes pour vous aider à démarrer votre projet.
Lecture du FIFO et demande de données
[modifier | modifier le wikicode]Voici le code source correspondant :
//******************************************************************************************************************************
// function readFIFOandAskForNextData()
// purpose: read FIFO and put data in array and start the request of next data
// arguments: array of unsigned char to store FIFO data
//
// return:
// note: you cannot understand if you never read the corresponding VHDL code (in io2.vhd)
//******************************************************************************************************************************
void readFIFOandAskForNextData(unsigned char nunchukDATA[]){
unsigned char i;
while(!(TWCR & (1<<TWINT))); // attente données dans le FIFO
for (i=0;i<6;i++) //lecture du Nunchuk du FIFO dans tableau
nunchukDATA[i]=TWDR;
// on demande une nouvelle lecture par le pérphérique : toutes données seront pour la prochaine fois
TWCR |= (1<<TWEN); //depart
TWCR &= ~(1<<TWEN);// arret pour attendre la prochaine fois : voir machine d'états pour comprendre
}
Il faudra le compléter par un code du genre :
readFIFOandAskForNextData(nunchukDATA);
if (nunchukDATA[0]>0xC0) {dxf = 2; dyf = 0;vPORTB &= 0xFC;
if (nunchukDATA[0]<0x30) {dxf = -2; dyf = 0;vPORTB = (vPORTB&0xFD)|0x01;}
if (nunchukDATA[1]>0xC0) {dyf = -4; dxf = 0;vPORTB = (vPORTB &0xFE)|0x02;}
if (nunchukDATA[1]<0x30) {dyf = 4; dxf = 0;vPORTB |= 0x03;}
pour gérer correctement les déplacements de pacman.
Attente d'un départ
[modifier | modifier le wikicode]Voici comment on peut bloquer tout en attendant un appui sur le bouton Z :
//******************************************************************************************************************************
// function waitStart()
// purpose: wait Z button start : Doesn't work properrly at the moment !!!
// arguments:
//
// return:
// note: you cannot understand if you never read the corresponding VHDL code (in io2.vhd)
//******************************************************************************************************************************
void waitStart(){
unsigned char data[6],loop=1;
while(loop) {
readFIFOandAskForNextData(data);
if ((data[5] & 0x01)==0x00) loop=0;
_delay_ms(10); // car data_present mal géré et on ne prend pas de risque
}
On rappelle pour comprendre le code, que les deux boutons sont peésents dans le 6° octet retourné par la Nunchuk (donc la case 5 de notre tableau) que les deux boutons sont les deux poids faibles et qu’ils sont à 1 lorsqu’ils ne sont pas appuyés.
Logiciel : gestion du deuxième ennemi
[modifier | modifier le wikicode]Nous avons ajouté un deuxième ennemi pour cette année scolaire. Votre programmation en C devra obligatoirement le gérer.
Comme nous avons l'intention d'examiner, à terme, la réalisation du pacman avec un petit système multitâche, nous demandons aux étudiants de cette année scolaire de lire l’article sur un système multitâche non préemptif et d'essayer d’utiliser les techniques correspondantes pour ce jeu. Ce système écrit par Ron Kreymborg a été porté pour les AVR ICI (mais avec au autre compilateur C). Téléchargez alors "multi.zip" pour avoir le code source.
Voici un sous-programme permettant de gérer le positionnement de notre deuxième ennemi :
//******************************************************************************************************************************
// function setenemy2XY()
// purpose: put the second enemy with x and y coordinates
// arguments:
// corresponding x and y coordinates
// return:
// note:
//******************************************************************************************************************************
void setenemy2XY(uint8_t x,unsigned char y){
ADCL=x;
ADCH=y;
}
Les sous-programmes donnés comme point de départ de l'année précédente peuvent être repris aussi.
Ressources pour commencer
[modifier | modifier le wikicode]On vous propose pour commencer votre projet un ensemble de ressources pour travailler. Voici ce que contient le fichier ReadMe.txt de la ressource :
Ce projet comporte :
- un processeur ATMega16 embarqué avec peu des périphériques originaux : seule la rs232 fonctionne
- un écran VGA gérant le pacman
- un périphérique gérant la manette Nunchuk de Nitendo.
- deux fichers ucf : un pour la carte spartan3 et un pour la carte spartan3E toutes deux de chez Digilent
Dans le répertoire Soft vous avez :
- un programme c ne gérant qu'un seul fantome sur les deux : pacman.c
- un autre programme c ne gérant qu'un seul fantome mais une poursuite plus sofistiquée du pacman par le fantome. Il a été réalisé par deux étudiants de GEII en projet 2012/2013 (Guillaume Demarquez et Pauline Skorupka) : pacmanEtu.c
- un script shell pour mettre le programme en question dans le processeur (par l'intermédiaire du fichier .bit) puis télécharger dans le FPGA.
Notez que la façon de télécharger utilisée dans le script n'est utilisable que sur un port parallèle. Si vous utilisez adept de digilent, il vous faudra tout adapter.
L'ensemble est téléchargeable ici :
Correction partielle
[modifier | modifier le wikicode]La correction matérielle complète est téléchargeable ici : nexys3PacmanAtmega16.zip pour la carte Nexys 3. Le programme C de cette correction est le mien mais les étudiantes (KONAK Sumeyye et SAVRY Maelle) vous présentent le leur ici dans le WIKI de l'IUT de Troyes. Pour une fois nous nous demandons sérieusement si la rédaction du rapport de ce projet ne dépasse pas en qualité celui du tuteur que vous avez sous les yeux ? Allez donc lire pour comparer.
La ressource nexys3PacmanAtmega16.zip évoquée ci-dessus a été mise à jour en octobre 2020 conformément à ce qui est présenté ici dans ce même chapitre. Nous avons mis à jour ce cœur pour qu'il puisse faire correctement les calculs en nombre flottants : deux instructions ne fonctionnaient pas correctement. Pour la petite histoire aucun calcul flottant n'est nécessaire pour le Pacman.
Voir aussi
[modifier | modifier le wikicode]Conception d'un périphérique i2c
[modifier | modifier le wikicode]La réalisation du périphérique Nunchuk pour le pacman de la section précédente nous a donné entière satisfaction. Mais elle est extrêmement spécialisée. Il existe beaucoup d'autres périphériques i2c mais il est impossible de les utiliser sans changer de machine d'états. Or changer une machine d'états est un travail complexe, bien plus complexe que d'utiliser un programme en C pour gérer un protocole i2c avec un microcontrôleur. C'est cette simplicité d'utilisation que nous allons rechercher dans cette section. En clair nous sommes obligés de refaire une machine d'états, mais ce sera, nous l'espérons la dernière. Toute modification du protocole i2c sera transférée sur le programme.
Dans la mesure du possible, ce que l'on va réaliser est très proche de ce que l'on trouve dans les ATMega8/16/32 du commerce. Nous allons utiliser trois registres (seulement contre 5 dans les ATMegas) pour réaliser les transferts i2c. Nous allons les nommer ici, par commodité, avec les noms qu'ils ont dans l'AVR :
- TWBR sert à régler la division d'horloge. Nous le ferons fonctionner différemment de la façon dont il fonctionne dans les AVR. Relisez l'étude de la manette Nunchuk et i2c dans les AVR pour comprendre la différence. La formule dans les AVR est tandis que celle dans notre cœur est .
- TWCR sert à commander le périphérique i2c. Nous allons garder les bits TWSTA, TWSTO, TWEN, TWEA et TWINT mais ajouter TWWR et TWRD respectivement pour écrire et lire.
- TWDR sert à écrire ou lire les données à transmettre ou reçues
Dans les AVR, il y a la possibilité d'envoyer un START seul. Par exemple le sous-programme suivant fait le travail :
//send start signal
void TWIStart(void)
{
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
while ((TWCR & (1<<TWINT)) == 0);
//TWCR &= ~(1<<TWSTA); //RAZ logiciel du bit TWSTA en commentaire avant des tests
}
Mais nous allons utiliser la partie VHDL que l'on a déjà utilisé pour la manette Nunchuk. Et elle ne nous offre pas la possibilité de séparer les START du write. Pour le STOP il en est autrement car il peut être associé à un read ou à un write. En clair ce que nous allons réaliser fonctionnera différemment de l'AVR et c'est d'ailleurs pour cela que nous avons ajouté les deux bits dans le registre TWCR, à savoir TWWR et TWRD.
Notre cahier des charges consiste à donner aux programmeurs les sept possibilités suivantes :
- write seul (TWEN=1 et TWWR=1 et TWSTA=0 et TWSTO=0)
- START + write (TWEN=1 et TWWR=1 et TWSTA=1 et TWSTO=0)
- write + STOP (TWEN=1 et TWWR=1 et TWSTA=0 et TWSTO=1)
- START + write + STOP (TWEN=1 et TWWR=1 et TWSTA=1 et TWSTO=1)
- read + ACK (TWEN=1 et TWRD=1 et TWEA=1 et TWSTO=0)
- read + ACK + STOP (TWEN=1 et TWRD=1 et TWEA=1 et TWSTO=1)
- read + NACK + STOP (TWEN=1 et TWRD=1 et TWEA=0 et TWSTO=1)
Ceci montre que notre machine d'états aura sept branches parallèles. C'est ce que montre la figure ci-contre que nous expliquerons un peu plus loin.
Le START + write + STOP juste au-dessus a été rajouté pour commander un périphérique particulier dans un autre chapitre. En principe ce n'est pas comme cela que se finit le protocole i2c. Mais ce périphérique n'est pas tout à fait de l'i2c. Il respecte le protocole i2c physique mais ne permet pas un partage de bus car on n'envoie pas d'adresse de périphérique.
Exercice 0
[modifier | modifier le wikicode]Cet exercice ne comporte pas de travail à proprement parler d'où son numéro 0.
Nous avons déjà présenté le graphe d'états. Ce qui est important à mémoriser de ce graphe est l'ensemble de ses huit branches liées aux sept possibilités données au programmeur. La huitième est là pour gérer des configurations de bits du registre TWCR incompatibles (par exemple une écriture et une lecture demandées simultanément).
Comme nous avons l'intention de réaliser un périphérique étape par étape, il faut comprendre sa liaison avec le microcontrôleur. Comme d'habitude un schéma explicatif est présenté. Il nous montre toutes les entrées du périphérique à réaliser (en jaune dans la figure). L'ensemble des exercices 1, 2, 3 et 4 se réalise dans ce rectangle jaune. Le schéma nous donne immédiatement l'entité correspondante :
entity 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 entity topi2c;
Distinguez les bits qui sont en "std_logic" des registres qui sont en "std_logic_vector(7 downto 0)".
Les bits vont servir à réaliser le premier exercice.
Exercice 1
[modifier | modifier le wikicode]Pour simplifier la programmation de la machine d'états, nous allons réaliser une fonction combinatoire qui sera capable de choisir une des huit branches présentes sur la machine d'états ci-dessus. On va pour cela utiliser quelques bits du registre de commande TWCR : la table de vérité à compléter vous précise lesquels.
1°) On vous demande de compléter la table de vérité ci-dessous capable de gérer les bits précédents et d'en sortir huit branches possibles :
- Table de vérité
Entrées | Sortie | |||||||||||
TWWR | TWRD | TWSTA | TWSTO | TWEA | branch1 | branch2 | branch3 | branch4 | branch5 | branch6 | branch7 | autrement |
1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? |
2°) Écrire le programme VHDL correspondant à l'aide d'un "with select when". Pour cela vous allez créer un signal interne en "std_logic_vector" pour les entrées à partir des entrées sous forme de bits (concaténation en VHDL réalisée avec l'opérateur &). La sortie sera un "std_logic_vector" qui regroupe aussi les diverses sorties de la table de vérité.
Indications :
- Une figure vous présente le travail à réaliser. Nous utiliserons à partir de maintenant des rectangles en pointillés pour désigner un composant non réalisé par le couple entité/architecture mais directement par equation ou process sur signaux externes ou internes.
- Les bits présentés dans cette table de vérité appartiennent tous au registre TWCR. On a :
- TWWR demande d'écriture i2c quand il est à 1
- TWRD demande de lecture i2c quand il est à 1
- TWSTA demande de Start i2c quand il est à 1. Ce bit ne peut pas être séparé de TWWR. Un start est toujours associé à une écriture.
- TWSTO demande de Stop i2c quand il est à 1. Ce bit est associé soit à TWWR soit à TWRD. Dans ce dernier cas on utilise aussi TWEA
- TWEA demande d'acquitement i2c quand il est à 1
- Le bit TWINT a été retiré car il a un fonctionnement très particulier et sera géré à part (voir exercice suivant)
- Le bit TWEN a été retiré parce qu'il est géré ailleurs comme d'habitude dans le "if clk'event"
- la gestion habituelle de ce genre de situation se fait directement dans les if des transitions de la machine d'états mais nous préférons la réaliser à l'extérieur de la machine d'état et avec une table de vérité complète, ce qui permettra de la programmer avec un "with select when"
- tous les bits non présents dans la description du cahier des charges ci-dessus seront supposés à 0.
- le "autrement" est à 1 quand aucune des autres branches n'est sélectionnée. Il n'y a donc que 6 lignes à compléter dans la table de vérité.
- si un programmeur demande une écriture et une lecture simultanément, c'est donc la branche "else" qui sera exécutée, c'est-à-dire qu'il ne se passera rien.
Voici une solution partielle. On y a laissé la déclaration des signaux utilisés et apparaît un begin de l'architecture... ceci dans l'idée d'éviter de câbler une entité. Ce qui est important est naturellement le with select when.
s_e : std_logic_vector(4 downto 0); --TWWR,TWRD,TWSTA,TWSTO,TWEA
s_select : std_logic_vector(7 downto 0); --branch1, ... ,branch7,autrement
begin
-- calcul combinatoire de la selection des branches de la machine d'états
s_e <= TWWR & TWRD & TWSTA & TWSTO & TWEA; --conforme a wikiversity
with s_e select
s_select <= "10000000" when "10000", -- write seul
"01000000" when "10010", -- write + stop
"00100000" when "10100", -- start + write
"00010000" when "10110", -- start + write + stop
"00001000" when "01001", -- read avec ack
"00000100" when "01011", -- read avec ack + stop
"00000010" when "01010", -- read avec nack + stop
"00000001" when others; -- autrement
Exercice 2 : Réalisation du bit TWINT
[modifier | modifier le wikicode]Attention : ce qui est présenté ici n'est pas général. C'est propre aux AVR ATMega et nous désirons réaliser un bit qui fonctionne à peu près comme cela.
Le bit TWINT du registre TWCR est donc un peu particulier. Il faut le mettre à un pour démarrer l'utilisation du cœur i2c. Mais comme il s'agit d'un drapeau, cette mise à 1 provoque en fait une mise à 0. C'est seulement quand la commande i2c demandée est réalisée que ce bit revient à 1. Ce mécanisme est là pour réaliser une interruption mais nous n'implanterons pas celle-ci.
Ce qui est à réaliser est encore une fois présenté à l'aide d'une figure dans laquelle on retrouve la partie de l'exercice 1 ainsi que la nouvelle pour l'exercice 2. Encore une fois le rectangle est en pointillés et son nom nous montre qu'il est réalisé par un process.
Réaliser ce mécanisme à l'aide d'un process qui reçoit un signal "TWINT", une horloge "clk", une entree "reset" et fabrique une sortie "s_TWINT".
Indications :
- le schéma de principe d'intégration du périphérique dans le processeur (figure présentée plus haut), nous montre que l'entrée TWINT est réalisée par l'équation
s_TWINT_tick <= I_WE_IO when ((I_ADR_IO = TWCR) and (I_DIN(7)='1')) else '0';
Ce nom suggère un tick (signal à 1 pendant une seule durée d'horloge). Ce signal est donc réalisé par l'écriture dans TWCR d'où la présence de "I_ADR_IO = TWCR" et par une écriture d'un 1 dans TWINT d'où la présence de "(I_DIN(7)='1')'. Tout ceci doit réaliser une remise à zéro (RAZ) d'un signal que l'on appellera s_TWINT.
- La mise à 1 (set) sera réalisée par la machine d'états (exercice suivant) qui génèrera le signal "O_set_TWINT"
On rappelle qu'un signal est un mécanisme très pratique pour réaliser des équations de récurrence car il n'est marqué ni entrée ni sortie. C'est pour cela que le code ci-dessous nécessite un signal "s_TWINT".
-- process de gestion du bit TWINT
process(clk,reset) begin
IF reset = '1' THEN
s_TWINT <= '0'; -- a declarer comme signal
elsif rising_edge(clk) then
if TWINT = '1' then -- plus de detection de front
s_TWINT <= '0';
elsif O_Set_TWINT = '1' then
s_TWINT <= '1';
end if;
end if;
end process;
O_TWINT <= s_TWINT;
Conséquences : L'utilisation du périphérique i2c se fera toujours en C à l'aide du couple :
TWCR = (1<<TWINT)|... a compléter...|(1<<TWEN);
while ((TWCR & (1<<TWINT)) == 0);
La première ligne sert à choisir la commande i2c que l'on veut réaliser tandis que la deuxième sert à attendre que tout soit fini.
Avant d'entamer la construction de la machine d'états il vous faut construire un timer et un registre de donnée. Ces deux process ont déjà été publiés avec la réalisation du périphérique Nunchuk et ne seront donc pas demandés ici mais sont présentés sur la figure ci-contre. Le registre comme le timer sont sur 8 bits. La sortie du timer est donc réalisée avec le bit 7 du timer.
Exercice 3 : Machine d'états
[modifier | modifier le wikicode]La machine d'états a été déjà présentée sous forme de figure mais nous la reproduisons encore une fois pour faciliter la lecture.
Les états de cette machine d'états sont représentés avec leur nom en partie supérieure et les action (sorties) en partie inférieure. Les sorties sont, pour tous les états, présentées dans le même ordre qui est indiqué sur la figure.
Explications :
- l'état "s_else" sert à réaliser le set du bit TWINT : dire au programmeur que ce qu'il a demandé de réaliser est complètement terminé
- un front montant sur TWINT permet le passage de l'état "s0" à l'état "s1". La façon dont a été réalisé ce front est expliquée dans l'exercice précédent.
- le choix des diverses branches (y compris le "autrement") est fait par l'ensemble des bits réunis dans un signal "s_select" réalisé en exercice 1
- les branches et donc les états ne sont pas dans l'ordre attendu (s_br1, s_br2, ... ,s_br6 et s_br7) pour simplifier le dessin
- l'état s_stop sert à réaliser le stop de l'i2c. Donc seules les branches qui ont à réaliser ce stop sont concernées par cet état.
Réaliser la machine d'états correspondante. Encore une fois l'ensemble est présenté sous forme de figure où vous reconnaîtrez la présence de la machine d'états dans un rectangle en pointillés car implanter directement dans un process.
--********** machine d'états ***************
PROCESS(clk,reset) BEGIN
IF reset = '1' THEN
state_reg <= s0;
ELSIF rising_edge(clk) THEN
IF TWEN = '1' THEN
state_reg <= state_next;
END IF;
END IF;
END PROCESS;
PROCESS(state_reg,TWINT,s_select,s_cmd_ack) BEGIN
state_next <= state_reg; -- default value
CASE state_reg IS
WHEN s0 => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF TWINT = '1' THEN
state_next <= s1;
ELSE
state_next <= s0;
END IF;
WHEN s1 => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF s_select(7) = '1' THEN --branche 1 : write seul
state_next <= s_br1;
ELSIF s_select(6) = '1' THEN --branche 2:write + stop
state_next <= s_br2;
ELSIF s_select(5) = '1' THEN --branche 3:start + write
state_next <= s_br3;
ELSIF s_select(4) = '1' THEN --branche 4:start + write + stop
state_next <= s_br4;
ELSIF s_select(3) = '1' THEN --branche 5:read avec ack
state_next <= s_br5;
ELSIF s_select(2) = '1' THEN --branche 6:read avec ack + stop
state_next <= s_br6;
ELSIF s_select(1) = '1' THEN --branche 7:read avec nack + stop
state_next <= s_br7;
ELSIF s_select(0) = '1' THEN --autrement
state_next <= s_else;
ELSE
state_next <= s_else;
END IF;
-- branche 1 : write seul
WHEN s_br1 => O_write <='1';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_else;
ELSE
state_next <= s_br1;
END IF;
-- branche 2 : write + stop
WHEN s_br2 => O_write <='1';O_start <='0';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='1';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_stop;
ELSE
state_next <= s_br2;
END IF;
-- branche 3 : start + write
WHEN s_br3 => O_write <='1';O_start <='1';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_else;
ELSE
state_next <= s_br3;
END IF;
-- branche 4 : start + write + stop
WHEN s_br4 => O_write <='1';O_start <='1';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='1';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_stop;
ELSE
state_next <= s_br4;
END IF;
-- branche 5 : read avec ack
WHEN s_br5 => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '1';O_ack_n<='0';s_timer_init<='0';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_ack;
ELSE
state_next <= s_br5;
END IF;
-- est-utile ? mais present dans la version opérationnelle
WHEN s_ack => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '1';O_ack_n<='0';s_timer_init<='0';write_res <='1';
state_next <= s_else;
-- branche 6 : read avec ack + stop
WHEN s_br6 => O_write <='0';O_start <='0';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '1';O_ack_n<='0';s_timer_init<='1';write_res <='1';
IF s_cmd_ack = '1' THEN
state_next <= s_stop;
ELSE
state_next <= s_br6;
END IF;
-- branche 7 : read avec nack + stop
WHEN s_br7 => O_write <='0';O_start <='0';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '1';O_ack_n<='1';s_timer_init<='1';write_res <='1';
IF s_cmd_ack = '1' THEN
state_next <= s_stop;
ELSE
state_next <= s_br7;
END IF;
WHEN s_stop => O_write <='0';O_start <='0';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF s_timer = '1' THEN
state_next <= s_else;
ELSE
state_next <= s_stop;
END IF;
-- state_next <= s_else;
-- autrement on en profite pour
WHEN s_else => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '1';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
state_next <= s0;
WHEN OTHERS => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
state_next <= s0;
END CASE;
END PROCESS;
Réalisation complète dans l'ATMega16
[modifier | modifier le wikicode]Voici notre choix des bits de contrôle du registre de contrôle TWCR. Ils ont été choisis pour être le plus possible en conformité aux AVR. On rappelle que TWWR et TWRD n'existent pas dans un ATMega.
TWCR
|
b7
|
b6
|
b5
|
b4
|
b3
|
b2
|
b1
|
b0
|
TWINT
|
TWEA
|
TWSTA
|
TWSTO
|
TWRD
|
TWEN
|
TWWR
|
?
|
La réalisation dans l'ATMega16 présentée dans ce livre nécessite de modifier 4 fichiers :
- réaliser une machine d'états autour du cœur i2c
- réaliser une interface au processeur
- propager les sorties i2c comme sortie du processeur
- modifier le fichier ucf pour prendre en compte ces modifications.
Exercice 4
[modifier | modifier le wikicode]Voici présenté sous forme de figure l'ensemble complet de ce qui est à réaliser.
Vous pouvez y distinguer le composant simple_i2c qui est celui qui a déjà été utilisé pour l'étude de la manette Nunchuk. Il n'est plus en pointillés car il est réalisé par un programme VHDL comportant une entité et une architecture.
Pour ne pas surcharger la figure, nous avons laissé quelques connexions non connectées :
- l'entrée TWBR est reliée à clk_cnt
- l'entrée nReset est reliée à "not reset". Le not est admis dans un port map
- l'entree "ena" et reliée à TWEN marqué TWEN[2] sur le schéma
Réaliser l'ensemble complet du périphérique.
--
-- Simple I2C controller
--
-- 1) No multimaster
-- 2) No slave mode
-- 3) No fifo's
--
-- notes:
-- Every command is acknowledged. Do not set a new command before previous is acknowledged.
-- Dout is available 1 clock cycle later as cmd_ack
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity 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 entity topi2c;
architecture arch_topi2c of topi2c is
component simple_i2c is
port (
clk : in std_logic;
ena : in std_logic;
nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
-- input signals
start,
stop,
read,
write,
ack_in : in std_logic;
Din : in std_logic_vector(7 downto 0);
-- output signals
cmd_ack : out std_logic;
ack_out : out std_logic;
Dout : out std_logic_vector(7 downto 0);
-- i2c signals
SCL : inout std_logic;
SDA : inout std_logic
);
end component simple_i2c;
signal s_e : std_logic_vector(4 downto 0); --TWWR,TWRD,TWSTA,TWSTO,TWEA
signal s_select : std_logic_vector(7 downto 0); --branch1, ... ,branch7,autrement
signal s_TWINT : std_logic;
signal O_start, O_stop, O_read, O_write, s_cmd_ack : std_logic;
signal O_Set_TWINT, O_ack_n : std_logic;
TYPE i2cperiph_state IS ( s0,s1,s_br1,s_br2,s_br3,s_br4,s_br5,s_br6,s_br7,
s_else,s_stop,s_ack); --, s_S_TWINT);
SIGNAL state_reg, state_next : i2cperiph_state;
SIGNAL timer : std_logic_vector(16 DOWNTO 0);
SIGNAL s_OUT_TWDR,Reg : std_logic_vector(7 DOWNTO 0);
SIGNAL s_timer,s_timer_init,write_res : std_logic;
begin
-- cablage coeur i2c
i2c:simple_i2c port map (
clk => clk,
ena => TWEN,
nReset => not reset,
clk_cnt => unsigned(TWBR), -- 5x SCL
-- input signals
start => O_start,
stop => O_stop,
read => O_read,
write => O_write,
ack_in => O_ack_n,
Din => IN_TWDR,
-- output signals
cmd_ack => s_cmd_ack,
ack_out => open,
Dout => s_OUT_TWDR,
-- i2c signals
SCL => SCL,
SDA => SDA
);
-- calcul combinatoire de la selection des branches de la machine d'états
s_e <= TWWR & TWRD & TWSTA & TWSTO & TWEA; --conforme a wikiversity
with s_e select
s_select <= "10000000" when "10000", --write seul
"01000000" when "10010", --write + stop
"00100000" when "10100", --start + write
"00010000" when "10110", --start + write + stop
"00001000" when "01001", --read avec ack
"00000100" when "01011", -- read avec ack + stop
"00000010" when "01010", -- read avec nack + stop
"00000001" when others; -- autrement
-- process de gestion du bit TWINT
process(clk,reset) begin
IF reset = '1' THEN
s_TWINT <= '0';
elsif rising_edge(clk) then
--if s_front_TWINT = '1' then
if TWINT = '1' then -- plus de detection de front
s_TWINT <= '0';
elsif O_Set_TWINT = '1' then
s_TWINT <= '1';
end if;
end if;
end process;
O_TWINT <= s_TWINT;
-- timer managment
PROCESS(clk) BEGIN
IF rising_edge(clk) THEN
IF s_timer_init='1' THEN -- timer_resettoChange
timer <= (others => '0');
ELSE
timer <= timer +1;
END IF;
END IF;
END PROCESS;
s_timer <= timer(10); --timer(16);
-- register managment
PROCESS(clk) BEGIN
IF rising_edge(clk) THEN
IF Reset ='1' THEN Reg <= "00000000";
ELSIF write_res='1' THEN
Reg <= s_out_twdr;
END IF;
END IF;
END PROCESS;
out_twdr <= Reg;
--********** machine d'états ***************
PROCESS(clk,reset) BEGIN
IF reset = '1' THEN
state_reg <= s0;
ELSIF rising_edge(clk) THEN
IF TWEN = '1' THEN
state_reg <= state_next;
END IF;
END IF;
END PROCESS;
PROCESS(state_reg,TWINT,s_select,s_cmd_ack) BEGIN
state_next <= state_reg; -- default value
CASE state_reg IS
WHEN s0 => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF TWINT = '1' THEN
state_next <= s1;
ELSE
state_next <= s0;
END IF;
WHEN s1 => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF s_select(7) = '1' THEN --branche 1 : write seul
state_next <= s_br1;
ELSIF s_select(6) = '1' THEN --branche 2:write + stop
state_next <= s_br2;
ELSIF s_select(5) = '1' THEN --branche 3:start + write
state_next <= s_br3;
ELSIF s_select(4) = '1' THEN --branche 4:start + write + stop
state_next <= s_br4;
ELSIF s_select(3) = '1' THEN --branche 5:read avec ack
state_next <= s_br5;
ELSIF s_select(2) = '1' THEN --branche 6:read avec ack + stop
state_next <= s_br6;
ELSIF s_select(1) = '1' THEN --branche 7:read avec nack + stop
state_next <= s_br7;
ELSIF s_select(0) = '1' THEN --autrement
state_next <= s_else;
ELSE
state_next <= s_else;
END IF;
-- branche 1 : write seul
WHEN s_br1 => O_write <='1';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_else;
ELSE
state_next <= s_br1;
END IF;
-- branche 2 : write + stop
WHEN s_br2 => O_write <='1';O_start <='0';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='1';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_stop;
ELSE
state_next <= s_br2;
END IF;
-- branche 3 : start + write
WHEN s_br3 => O_write <='1';O_start <='1';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_else;
ELSE
state_next <= s_br3;
END IF;
-- branche 4 : start + write + stop
WHEN s_br4 => O_write <='1';O_start <='1';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='1';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_stop;
ELSE
state_next <= s_br4;
END IF;
-- branche 5 : read avec ack
WHEN s_br5 => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '1';O_ack_n<='0';s_timer_init<='0';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_ack;
ELSE
state_next <= s_br5;
END IF;
-- est-utile ? mais present dans la version opérationnelle
WHEN s_ack => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '1';O_ack_n<='0';s_timer_init<='0';write_res <='1';
state_next <= s_else;
-- branche 6 : read avec ack + stop
WHEN s_br6 => O_write <='0';O_start <='0';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '1';O_ack_n<='0';s_timer_init<='1';write_res <='1';
IF s_cmd_ack = '1' THEN
state_next <= s_stop;
ELSE
state_next <= s_br6;
END IF;
-- branche 7 : read avec nack + stop
WHEN s_br7 => O_write <='0';O_start <='0';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '1';O_ack_n<='1';s_timer_init<='1';write_res <='1';
IF s_cmd_ack = '1' THEN
state_next <= s_stop;
ELSE
state_next <= s_br7;
END IF;
WHEN s_stop => O_write <='0';O_start <='0';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF s_timer = '1' THEN
state_next <= s_else;
ELSE
state_next <= s_stop;
END IF;
-- state_next <= s_else;
-- autrement on en profite pour
WHEN s_else => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '1';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
state_next <= s0;
WHEN OTHERS => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
state_next <= s0;
END CASE;
END PROCESS;
end arch_topi2c;
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
entity simple_i2c is
port (
clk : in std_logic;
ena : in std_logic;
nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
-- input signals
start,
stop,
read,
write,
ack_in : in std_logic;
Din : in std_logic_vector(7 downto 0);
-- output signals
cmd_ack : out std_logic;
ack_out : out std_logic;
Dout : out std_logic_vector(7 downto 0);
-- i2c signals
SCL : inout std_logic;
SDA : inout std_logic
);
end entity simple_i2c;
architecture structural of simple_i2c is
component i2c_core is
port (
clk : in std_logic;
nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
cmd : in std_logic_vector(2 downto 0);
cmd_ack : out std_logic;
busy : out std_logic;
Din : in std_logic;
Dout : out std_logic;
SCL : inout std_logic;
SDA : inout std_logic
);
end component i2c_core;
-- commands for i2c_core
constant CMD_NOP : std_logic_vector(2 downto 0) := "000";
constant CMD_START : std_logic_vector(2 downto 0) := "010";
constant CMD_STOP : std_logic_vector(2 downto 0) := "011";
constant CMD_READ : std_logic_vector(2 downto 0) := "100";
constant CMD_WRITE : std_logic_vector(2 downto 0) := "101";
-- signals for i2c_core
signal core_cmd : std_logic_vector(2 downto 0);
signal core_ack, core_busy, core_txd, core_rxd : std_logic;
-- signals for shift register
signal sr : std_logic_vector(7 downto 0); -- 8bit shift register
signal shift, ld : std_logic;
-- signals for state machine
signal go, host_ack : std_logic;
begin
-- hookup i2c core
u1: i2c_core port map (clk, nReset, clk_cnt, core_cmd, core_ack, core_busy, core_txd, core_rxd, SCL, SDA);
-- generate host-command-acknowledge
cmd_ack <= host_ack;
-- generate go-signal
go <= (read or write) and not host_ack;
-- assign Dout output to shift-register
Dout <= sr;
-- assign ack_out output to core_rxd (contains last received bit)
ack_out <= core_rxd;
-- generate shift register
shift_register: process(clk)
begin
if (clk'event and clk = '1') then
if (ld = '1') then
sr <= din;
elsif (shift = '1') then
sr <= (sr(6 downto 0) & core_rxd);
end if;
end if;
end process shift_register;
--
-- state machine
--
statemachine : block
type states is (st_idle, st_start, st_read, st_write, st_ack, st_stop);
signal state : states;
signal dcnt : unsigned(2 downto 0);
begin
--
-- command interpreter, translate complex commands into simpler I2C commands
--
nxt_state_decoder: process(clk, nReset, state, core_ack, stop)
variable nxt_state : states;
variable idcnt : unsigned(2 downto 0);
variable ihost_ack : std_logic;
variable icore_cmd : std_logic_vector(2 downto 0);
variable icore_txd : std_logic;
variable ishift, iload : std_logic;
begin
-- 8 databits (1byte) of data to shift-in/out
idcnt := dcnt;
-- no acknowledge (until command complete)
ihost_ack := '0';
icore_txd := core_txd;
-- keep current command to i2c_core
icore_cmd := core_cmd;
-- no shifting or loading of shift-register
ishift := '0';
iload := '0';
-- keep current state;
nxt_state := state;
case state is
when st_idle =>
if (go = '1') then
if (start = '1') then
nxt_state := st_start;
icore_cmd := CMD_START;
elsif (read = '1') then
nxt_state := st_read;
icore_cmd := CMD_READ;
idcnt := "111";
else
nxt_state := st_write;
icore_cmd := CMD_WRITE;
idcnt := "111";
iload := '1';
end if;
end if;
when st_start =>
if (core_ack = '1') then
if (read = '1') then
nxt_state := st_read;
icore_cmd := CMD_READ;
idcnt := "111";
else
nxt_state := st_write;
icore_cmd := CMD_WRITE;
idcnt := "111";
iload := '1';
end if;
end if;
when st_write =>
if (core_ack = '1') then
idcnt := dcnt -1; -- count down Data_counter
icore_txd := sr(7);
if (dcnt = 0) then
nxt_state := st_ack;
icore_cmd := CMD_READ; -- for ack receive
else
ishift := '1';
-- icore_txd := sr(7);
end if;
end if;
when st_read =>
if (core_ack = '1') then
idcnt := dcnt -1; -- count down Data_counter
ishift := '1';
if (dcnt = 0) then
nxt_state := st_ack;
icore_cmd := CMD_WRITE;-- for ack send
icore_txd := ack_in;
end if;
end if;
when st_ack =>
if (core_ack = '1') then
-- generate command acknowledge signal
ihost_ack := '1';
-- Perform an additional shift, needed for 'read' (store last received bit in shift register)
ishift := '1';
-- check for stop; Should a STOP command be generated ?
if (stop = '1') then
nxt_state := st_stop;
icore_cmd := CMD_STOP;
else
nxt_state := st_idle;
icore_cmd := CMD_NOP;
end if;
end if;
when st_stop =>
if (core_ack = '1') then
nxt_state := st_idle;
icore_cmd := CMD_NOP;
end if;
when others => -- illegal states
nxt_state := st_idle;
icore_cmd := CMD_NOP;
end case;
-- generate registers
if (nReset = '0') then
core_cmd <= CMD_NOP;
core_txd <= '0';
shift <= '0';
ld <= '0';
dcnt <= "111";
host_ack <= '0';
state <= st_idle;
elsif (clk'event and clk = '1') then
if (ena = '1') then
state <= nxt_state;
dcnt <= idcnt;
shift <= ishift;
ld <= iload;
core_cmd <= icore_cmd;
core_txd <= icore_txd;
host_ack <= ihost_ack;
end if;
end if;
end process nxt_state_decoder;
end block statemachine;
end architecture structural;
--
--
-- I2C Core
--
-- Translate simple commands into SCL/SDA transitions
-- Each command has 5 states, A/B/C/D/idle
--
-- start: SCL ~~~~~~~~~~\____
-- SDA ~~~~~~~~\______
-- x | A | B | C | D | i
--
-- repstart SCL ____/~~~~\___
-- SDA __/~~~\______
-- x | A | B | C | D | i
--
-- stop SCL ____/~~~~~~~~
-- SDA ==\____/~~~~~
-- x | A | B | C | D | i
--
--- write SCL ____/~~~~\____
-- SDA ==X=========X=
-- x | A | B | C | D | i
--
--- read SCL ____/~~~~\____
-- SDA XXXX=====XXXX
-- x | A | B | C | D | i
--
-- Timing: Normal mode Fast mode
-----------------------------------------------------------------
-- Fscl 100KHz 400KHz
-- Th_scl 4.0us 0.6us High period of SCL
-- Tl_scl 4.7us 1.3us Low period of SCL
-- Tsu:sta 4.7us 0.6us setup time for a repeated start condition
-- Tsu:sto 4.0us 0.6us setup time for a stop condition
-- Tbuf 4.7us 1.3us Bus free time between a stop and start condition
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
USE ieee.std_logic_unsigned.all;
entity i2c_core is
port (
clk : in std_logic;
nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
cmd : in std_logic_vector(2 downto 0);
cmd_ack : out std_logic;
busy : out std_logic;
Din : in std_logic;
Dout : out std_logic;
SCL : inout std_logic;
SDA : inout std_logic
);
end entity i2c_core;
architecture structural of i2c_core is
constant CMD_NOP : std_logic_vector(2 downto 0) := "000";
constant CMD_START : std_logic_vector(2 downto 0) := "010";
constant CMD_STOP : std_logic_vector(2 downto 0) := "011";
constant CMD_READ : std_logic_vector(2 downto 0) := "100";
constant CMD_WRITE : std_logic_vector(2 downto 0) := "101";
type cmds is (idle, start_a, start_b, start_c, start_d, stop_a, stop_b, stop_c, rd_a, rd_b, rd_c, rd_d, wr_a, wr_b, wr_c, wr_d);
signal state : cmds;
signal SDAo, SCLo : std_logic;
signal txd : std_logic;
signal clk_en, slave_wait :std_logic;
signal cnt : unsigned(7 downto 0) := clk_cnt;
-- signal cnt : std_logic_vector(7 downto 0); -- := clk_cnt;
begin
-- whenever the slave is not ready it can delay the cycle by pulling SCL low
slave_wait <= '1' when ((SCLo = '1') and (SCL = '0')) else '0';
-- generate clk enable signal
gen_clken: process(clk, nReset)
begin
if (nReset = '0') then
cnt <= (others => '0');
clk_en <= '1'; --'0';
elsif (clk'event and clk = '1') then
if (cnt = 0) then
clk_en <= '1';
cnt <= clk_cnt;
else
if (slave_wait = '0') then
cnt <= cnt -1;
end if;
clk_en <= '0';
end if;
end if;
end process gen_clken;
-- generate statemachine
nxt_state_decoder : process (clk, nReset, state, cmd, SDA)
variable nxt_state : cmds;
variable icmd_ack, ibusy, store_sda : std_logic;
variable itxd : std_logic;
begin
nxt_state := state;
icmd_ack := '0'; -- default no acknowledge
ibusy := '1'; -- default busy
store_sda := '0';
itxd := txd;
case (state) is
-- idle
when idle =>
case cmd is
when CMD_START =>
nxt_state := start_a;
icmd_ack := '1'; -- command completed
when CMD_STOP =>
nxt_state := stop_a;
icmd_ack := '1'; -- command completed
when CMD_WRITE =>
nxt_state := wr_a;
icmd_ack := '1'; -- command completed
itxd := Din;
when CMD_READ =>
nxt_state := rd_a;
icmd_ack := '1'; -- command completed
when others =>
nxt_state := idle;
-- don't acknowledge NOP command icmd_ack := '1'; -- command completed
ibusy := '0';
end case;
-- start
when start_a =>
nxt_state := start_b;
when start_b =>
nxt_state := start_c;
when start_c =>
nxt_state := start_d;
when start_d =>
nxt_state := idle;
ibusy := '0'; -- not busy when idle
-- stop
when stop_a =>
nxt_state := stop_b;
when stop_b =>
nxt_state := stop_c;
when stop_c =>
-- nxt_state := stop_d;
-- when stop_d =>
nxt_state := idle;
ibusy := '0'; -- not busy when idle
-- read
when rd_a =>
nxt_state := rd_b;
when rd_b =>
nxt_state := rd_c;
when rd_c =>
nxt_state := rd_d;
store_sda := '1';
when rd_d =>
nxt_state := idle;
ibusy := '0'; -- not busy when idle
-- write
when wr_a =>
nxt_state := wr_b;
when wr_b =>
nxt_state := wr_c;
when wr_c =>
nxt_state := wr_d;
when wr_d =>
nxt_state := idle;
ibusy := '0'; -- not busy when idle
end case;
-- generate regs
if (nReset = '0') then
state <= idle;
cmd_ack <= '0';
busy <= '0';
txd <= '0';
Dout <= '0';
elsif (clk'event and clk = '1') then
if (clk_en = '1') then
state <= nxt_state;
busy <= ibusy;
txd <= itxd;
if (store_sda = '1') then
Dout <= SDA;
end if;
end if;
cmd_ack <= icmd_ack and clk_en;
end if;
end process nxt_state_decoder;
--
-- convert states to SCL and SDA signals
--
output_decoder: process (clk, nReset, state)
variable iscl, isda : std_logic;
begin
case (state) is
when idle =>
iscl := SCLo; -- keep SCL in same state
isda := SDA; -- keep SDA in same state
-- start
when start_a =>
iscl := SCLo; -- keep SCL in same state (for repeated start)
isda := '1'; -- set SDA high
when start_b =>
iscl := '1'; -- set SCL high
isda := '1'; -- keep SDA high
when start_c =>
iscl := '1'; -- keep SCL high
isda := '0'; -- sel SDA low
when start_d =>
iscl := '0'; -- set SCL low
isda := '0'; -- keep SDA low
-- stop
when stop_a =>
iscl := '0'; -- keep SCL disabled
isda := '0'; -- set SDA low
when stop_b =>
iscl := '1'; -- set SCL high
isda := '0'; -- keep SDA low
when stop_c =>
iscl := '1'; -- keep SCL high
isda := '1'; -- set SDA high
-- write
when wr_a =>
iscl := '0'; -- keep SCL low
-- isda := txd; -- set SDA
isda := Din;
when wr_b =>
iscl := '1'; -- set SCL high
-- isda := txd; -- set SDA
isda := Din;
when wr_c =>
iscl := '1'; -- keep SCL high
-- isda := txd; -- set SDA
isda := Din;
when wr_d =>
iscl := '0'; -- set SCL low
-- isda := txd; -- set SDA
isda := Din;
-- read
when rd_a =>
iscl := '0'; -- keep SCL low
isda := '1'; -- tri-state SDA
when rd_b =>
iscl := '1'; -- set SCL high
isda := '1'; -- tri-state SDA
when rd_c =>
iscl := '1'; -- keep SCL high
isda := '1'; -- tri-state SDA
when rd_d =>
iscl := '0'; -- set SCL low
isda := '1'; -- tri-state SDA
end case;
-- generate registers
if (nReset = '0') then
SCLo <= '1';
SDAo <= '1';
elsif (clk'event and clk = '1') then
if (clk_en = '1') then
SCLo <= iscl;
SDAo <= isda;
end if;
end if;
end process output_decoder;
SCL <= '0' when (SCLo = '0') else 'Z'; -- since SCL is externally pulled-up convert a '1' to a 'Z'(tri-state)
SDA <= '0' when (SDAo = '0') else 'Z'; -- since SDA is externally pulled-up convert a '1' to a 'Z'(tri-state)
-- SCL <= SCLo;
-- SDA <= SDAo;
end architecture structural;
-- Listing 5.3 : Pong P. Chu
-- FPGA Prototyping by VHDL Examples
-- Wiley 2008
library ieee;
use ieee.std_logic_1164.all;
entity edge_detect is
port(
clk, reset: in std_logic;
level: in 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';
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 =>
if level= '0' then
state_next <= zero;
end if;
end case;
end process;
end moore_arch;
Exercice 5
[modifier | modifier le wikicode]Interfacer le périphérique de l'exercice 4 dans votre ATMega16.
-------------------------------------------------------------------------------
--
-- 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;
-- 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;
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;
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
--
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);
-- 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
--> removed 2011/10/18
-- Q_LEDS(1) <= L_LEDS;
-- Q_LEDS(0) <= not L_LEDS;
--<
Q_INTVEC <= L_INTVEC;
end Behavioral;
Exercice 6
[modifier | modifier le wikicode]Adapter votre processeur au nouveau périphérique. Il suffit ici de sortir les file de l'i2c vers l'extérieur.
-------------------------------------------------------------------------------
--
-- 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;
-- i2c signals
SCL : inout std_logic;
SDA : inout 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; -- i2c signals
SCL : inout std_logic;
SDA : inout 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'; --{{unité|50|MHz}}
--signal L_CLK_12_5 : std_logic := '0'; --12.{{unité|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, -- {{unité|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, --{{unité|50|MHz}}
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,
SDA => SDA,
SCL => SCL);
-- input clock scaler
--
clk_div : process(I_CLK_100,l_clk_cnt) --I_CLK_50 : {{unité|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;
L_CLR_N <= I_CLR;
end Behavioral;
Exercice 7
[modifier | modifier le wikicode]Modifier le fichier ucf pour l'adapter à votre carte.
## Clock signal NET "I_CLK_100" LOC = "V10" | IOSTANDARD = "LVCMOS33"; #Bank = 2, pin name = IO_L30N_GCLK0_USERCCLK, Sch name = GCLK Net "I_CLK_100" TNM_NET = sys_clk_pin; TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin {{unité|100000|kHz}}; ## Leds NET "Q_PORTC<0>" LOC = "U16" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L2P_CMPCLK, Sch name = LD0 NET "Q_PORTC<1>" LOC = "V16" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L2N_CMPMOSI, Sch name = LD1 NET "Q_PORTC<2>" LOC = "U15" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L5P, Sch name = LD2 NET "Q_PORTC<3>" LOC = "V15" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L5N, Sch name = LD3 NET "Q_PORTC<4>" LOC = "M11" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L15P, Sch name = LD4 NET "Q_PORTC<5>" LOC = "N11" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L15N, Sch name = LD5 NET "Q_PORTC<6>" LOC = "R11" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L16P, Sch name = LD6 NET "Q_PORTC<7>" LOC = "T11" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L16N_VREF, Sch name = LD7 ## Switches NET "I_CLR" LOC = "T10" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L29N_GCLK2, Sch name = SW0 #NET "sw<1>" LOC = "T9" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L32P_GCLK29, Sch name = SW1 #NET "sw<2>" LOC = "V9" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L32N_GCLK28, Sch name = SW2 #NET "sw<3>" LOC = "M8" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L40P, Sch name = SW3 #NET "sw<4>" LOC = "N8" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L40N, Sch name = SW4 #NET "sw<5>" LOC = "U8" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L41P, Sch name = SW5 #NET "sw<6>" LOC = "V8" | IOSTANDARD = "LVCMOS33"; #Bank = 2, Pin name = IO_L41N_VREF, Sch name = SW6 #NET "sw<7>" LOC = "T5" | IOSTANDARD = "LVCMOS33"; #Bank = MISC, Pin name = IO_L48N_RDWR_B_VREF_2, Sch name = SW7 ##JC #NET "JC<0>" LOC = "H3" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L44N_GCLK20_M3A6, Sch name = JC1 #NET "JC<1>" LOC = "L7" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L45P_M3A3, Sch name = JC2 NET "SCL" LOC = "K6" | IOSTANDARD = LVTTL | PULLUP ;#IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L45N_M3ODT, Sch name = JC3 NET "SDA" LOC = "G3" | IOSTANDARD = LVTTL | PULLUP ;#IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L46P_M3CLK, Sch name = JC4 #NET "JC<4>" LOC = "G1" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L46N_M3CLKN, Sch name = JC7 #NET "JC<5>" LOC = "J7" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L47P_M3A0, Sch name = JC8 #NET "JC<6>" LOC = "J6" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L47N_M3A1, Sch name = JC9 #NET "JC<7>" LOC = "F2" | IOSTANDARD = "LVCMOS33"; #Bank = 3, Pin name = IO_L48P_M3BA0, Sch name = JC10 ## Usb-RS232 interface NET "I_Rx" LOC = "N17" | IOSTANDARD = "LVCMOS33"; #Bank = 1, Pin name = IO_L48P_HDC_M1DQ8, Sch name = MCU-RX NET "Q_Tx" LOC = "N18" | IOSTANDARD = "LVCMOS33"; #Bank = 1, Pin name = IO_L48N_M1DQ9, Sch name = MCU-TX
Nous avons eu l'occasion de tester ce cœur I2C avec la manette Nunchuck, le MPU6050 un thermomètre i2C. Mais faire fonctionner un capteur laser "Time of Flight" de type VL53L0X nous a obligé à utiliser un analyseur logique. Il nous a montré quelques défauts :
- glitch qui apparaît avec les instructions TWIWriteStop et TWIReadACK (sans effet car pas présent quand horloge est haute)
- TWIStop non fonctionnel (en fait non implémenté dans la machine d'états)
- l'étude de ce capteur VL53L0X avec un Arduino nous a fait apparaître des temporisations matérielles (d'environ 10us) entre chacun des transferts i2c.
- L'instruction TWIReadNACK semble importante à implémenter et peut être que TWIReadAck ne s'arrête pas correctement
Quelques modifications sont donc nécessaires mais nous pensons ce cœur assez avancé pour une utilisation avec beaucoup de capteurs i2c.
Amélioration du cœur i2c
[modifier | modifier le wikicode]La remarque précédente nous a obligé à améliorer le code de ce cœur i2c. Nous laissons pour le moment le code source de cette nouvelle version ici sans commentaire, sachant qu'elle n'a pas encore été testée.
--
-- Simple I2C controller
--
-- 1) No multimaster
-- 2) No slave mode
-- 3) No fifo's
--
-- notes:
-- Every command is acknowledged. Do not set a new command before previous is acknowledged.
-- Dout is available 1 clock cycle later as cmd_ack
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity 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 entity topi2c;
architecture arch_topi2c of topi2c is
component simple_i2c is
port (
clk : in std_logic;
ena : in std_logic;
nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
-- input signals
start,
stop,
read,
write,
ack_in : in std_logic;
Din : in std_logic_vector(7 downto 0);
-- output signals
cmd_ack : out std_logic;
ack_out : out std_logic;
Dout : out std_logic_vector(7 downto 0);
-- i2c signals
SCL : inout std_logic;
SDA : inout std_logic
);
end component simple_i2c;
signal s_e : std_logic_vector(4 downto 0); --TWWR,TWRD,TWSTA,TWSTO,TWEA
signal s_select : std_logic_vector(9 downto 0); --branch1, ... ,branch9,autrement
signal s_TWINT : std_logic;
signal O_start, O_stop, O_read, O_write, s_cmd_ack : std_logic;
signal O_Set_TWINT, O_ack_n : std_logic;
TYPE i2cperiph_state IS ( s0,s1,s2,s_br1,s_br2,s_br3,s_br4,s_br5,s_br6,s_br7,s_br8,
s_br9,s_else,s_stop,s_ack); --, s_S_TWINT);
SIGNAL state_reg, state_next : i2cperiph_state;
SIGNAL timer : std_logic_vector(7 DOWNTO 0);
SIGNAL s_OUT_TWDR,Reg : std_logic_vector(7 DOWNTO 0);
SIGNAL s_timer,s_timer_init,write_res : std_logic;
begin
-- cablage coeur i2c
i2c:simple_i2c port map (
clk => clk,
ena => TWEN,
nReset => not reset,
clk_cnt => unsigned(TWBR), -- 5x SCL
-- input signals
start => O_start,
stop => O_stop,
read => O_read,
write => O_write,
ack_in => O_ack_n,
Din => IN_TWDR,
-- output signals
cmd_ack => s_cmd_ack,
ack_out => open,
Dout => s_OUT_TWDR,
-- i2c signals
SCL => SCL,
SDA => SDA
);
-- calcul combinatoire de la selection des branches de la machine d'états
s_e <= TWWR & TWRD & TWSTA & TWSTO & TWEA; --conforme a wikiversity
with s_e select
s_select <= "0010000000" when "10000", --write seul
"0001000000" when "10010", --write + stop
"0000100000" when "10100", --start + write
"0000010000" when "10110", --start + write + stop
"0000001000" when "01001", --read avec ack
"0000000100" when "01011", -- read avec ack + stop
"0000000010" when "01010", -- read avec nack + stop
"0100000000" when "00010", -- stop seul
"1000000000" when "01000", -- read avec nack
"0000000001" when others; -- autrement
-- process de gestion du bit TWINT
process(clk,reset) begin
IF reset = '1' THEN
s_TWINT <= '0';
elsif rising_edge(clk) then
--if s_front_TWINT = '1' then
if TWINT = '1' then -- plus de detection de front
s_TWINT <= '0';
elsif O_Set_TWINT = '1' then
s_TWINT <= '1';
end if;
end if;
end process;
O_TWINT <= s_TWINT;
-- timer managment
PROCESS(clk) BEGIN
IF rising_edge(clk) THEN
IF s_timer_init='1' THEN -- timer_resettoChange
timer <= (others => '0');
ELSE
timer <= timer +1;
END IF;
END IF;
END PROCESS;
s_timer <= timer(7);
-- register managment
PROCESS(clk) BEGIN
IF rising_edge(clk) THEN
IF Reset ='1' THEN Reg <= "00000000";
ELSIF write_res='1' THEN
Reg <= s_out_twdr;
END IF;
END IF;
END PROCESS;
out_twdr <= Reg;
--********** machine d'états ***************
PROCESS(clk,reset) BEGIN
IF reset = '1' THEN
state_reg <= s0;
ELSIF rising_edge(clk) THEN
IF TWEN = '1' THEN
state_reg <= state_next;
END IF;
END IF;
END PROCESS;
PROCESS(state_reg,TWINT,s_select,s_cmd_ack) BEGIN
state_next <= state_reg; -- default value
CASE state_reg IS
WHEN s0 => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='1';write_res <='0';
IF TWINT = '1' THEN
state_next <= s1;
ELSE
state_next <= s0;
END IF;
WHEN s1 => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF s_timer = '1' THEN
state_next <= s2; -- on passe à la suite
ELSE
state_next <= s1; -- on attend (2eme version)
END IF;
WHEN s2 => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF s_select(7) = '1' THEN --branche 1 : write seul
state_next <= s_br1;
ELSIF s_select(6) = '1' THEN --branche 2:write + stop
state_next <= s_br2;
ELSIF s_select(5) = '1' THEN --branche 3:start + write
state_next <= s_br3;
ELSIF s_select(4) = '1' THEN --branche 4:start + write + stop
state_next <= s_br4;
ELSIF s_select(3) = '1' THEN --branche 5:read avec ack
state_next <= s_br5;
ELSIF s_select(2) = '1' THEN --branche 6:read avec ack + stop
state_next <= s_br6;
ELSIF s_select(1) = '1' THEN --branche 7:read avec nack + stop
state_next <= s_br7;
ELSIF s_select(8) = '1' THEN --stop seul
state_next <= s_br8;
ELSIF s_select(9) = '1' THEN --branche 8:read+NACK
state_next <= s_br9;
ELSIF s_select(0) = '1' THEN --autrement
state_next <= s_else;
ELSE
state_next <= s_else;
END IF;
-- branche 1 : write seul
WHEN s_br1 => O_write <='1';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_else;
ELSE
state_next <= s_br1;
END IF;
-- branche 2 : write + stop
WHEN s_br2 => O_write <='1';O_start <='0';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='1';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_stop;
ELSE
state_next <= s_br2;
END IF;
-- branche 3 : start + write
WHEN s_br3 => O_write <='1';O_start <='1';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_else;
ELSE
state_next <= s_br3;
END IF;
-- branche 4 : start + write + stop
WHEN s_br4 => O_write <='1';O_start <='1';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='1';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_stop;
ELSE
state_next <= s_br4;
END IF;
-- branche 5 : read avec ack
WHEN s_br5 => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '1';O_ack_n<='0';s_timer_init<='0';write_res <='0';
IF s_cmd_ack = '1' THEN
state_next <= s_ack;
ELSE
state_next <= s_br5;
END IF;
-- est-utile ? mais present dans la version opérationnelle
WHEN s_ack => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '1';O_ack_n<='0';s_timer_init<='0';write_res <='1';
state_next <= s_else;
-- branche 6 : read avec ack + stop
WHEN s_br6 => O_write <='0';O_start <='0';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '1';O_ack_n<='0';s_timer_init<='1';write_res <='1';
IF s_cmd_ack = '1' THEN
state_next <= s_stop;
ELSE
state_next <= s_br6;
END IF;
-- branche 7 : read avec nack + stop
WHEN s_br7 => O_write <='0';O_start <='0';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '1';O_ack_n<='1';s_timer_init<='1';write_res <='1';
IF s_cmd_ack = '1' THEN
state_next <= s_stop;
ELSE
state_next <= s_br7;
END IF;
-- branche 8 : STOP seul uniquement declenchement timer ici
WHEN s_br8 => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='1';write_res <='0';
state_next <= s_stop;
-- branche 9 : read avec nack
WHEN s_br9 => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '1';O_ack_n<='1';s_timer_init<='0';write_res <='1';
IF s_cmd_ack = '1' THEN
state_next <= s_else;
ELSE
state_next <= s_br9;
END IF;
WHEN s_stop => O_write <='0';O_start <='0';O_stop <='1';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
IF s_timer = '1' THEN -- impossible avec IF s_cmd_ack = '1' THEN
state_next <= s_else;
ELSE
state_next <= s_stop;
END IF;
-- state_next <= s_else;
-- autrement on en profite pour
WHEN s_else => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '1';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
state_next <= s0;
WHEN OTHERS => O_write <='0';O_start <='0';O_stop <='0';O_Set_TWINT <= '0';
O_read <= '0';O_ack_n<='1';s_timer_init<='0';write_res <='0';
state_next <= s0;
END CASE;
END PROCESS;
end arch_topi2c;
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
entity simple_i2c is
port (
clk : in std_logic;
ena : in std_logic;
nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
-- input signals
start,
stop,
read,
write,
ack_in : in std_logic;
Din : in std_logic_vector(7 downto 0);
-- output signals
cmd_ack : out std_logic;
ack_out : out std_logic;
Dout : out std_logic_vector(7 downto 0);
-- i2c signals
SCL : inout std_logic;
SDA : inout std_logic
);
end entity simple_i2c;
architecture structural of simple_i2c is
component i2c_core is
port (
clk : in std_logic;
nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
cmd : in std_logic_vector(2 downto 0);
cmd_ack : out std_logic;
busy : out std_logic;
Din : in std_logic;
Dout : out std_logic;
SCL : inout std_logic;
SDA : inout std_logic
);
end component i2c_core;
-- commands for i2c_core
constant CMD_NOP : std_logic_vector(2 downto 0) := "000";
constant CMD_START : std_logic_vector(2 downto 0) := "010";
constant CMD_STOP : std_logic_vector(2 downto 0) := "011";
constant CMD_READ : std_logic_vector(2 downto 0) := "100";
constant CMD_WRITE : std_logic_vector(2 downto 0) := "101";
-- signals for i2c_core
signal core_cmd : std_logic_vector(2 downto 0);
signal core_ack, core_busy, core_txd, core_rxd : std_logic;
-- signals for shift register
signal sr : std_logic_vector(7 downto 0); -- 8bit shift register
signal shift, ld : std_logic;
-- signals for state machine
signal go, host_ack : std_logic;
begin
-- hookup i2c core
u1: i2c_core port map (clk, nReset, clk_cnt, core_cmd, core_ack, core_busy, core_txd, core_rxd, SCL, SDA);
-- generate host-command-acknowledge
cmd_ack <= host_ack;
-- generate go-signal
go <= (read or write) and not host_ack;
-- assign Dout output to shift-register
Dout <= sr;
-- assign ack_out output to core_rxd (contains last received bit)
ack_out <= core_rxd;
-- generate shift register
shift_register: process(clk)
begin
if (clk'event and clk = '1') then
if (ld = '1') then
sr <= din;
elsif (shift = '1') then
sr <= (sr(6 downto 0) & core_rxd);
end if;
end if;
end process shift_register;
--
-- state machine
--
statemachine : block
type states is (st_idle, st_start, st_read, st_write, st_ack, st_stop);
signal state : states;
signal dcnt : unsigned(2 downto 0);
begin
--
-- command interpreter, translate complex commands into simpler I2C commands
--
nxt_state_decoder: process(clk, nReset, state, core_ack, stop)
variable nxt_state : states;
variable idcnt : unsigned(2 downto 0);
variable ihost_ack : std_logic;
variable icore_cmd : std_logic_vector(2 downto 0);
variable icore_txd : std_logic;
variable ishift, iload : std_logic;
begin
-- 8 databits (1byte) of data to shift-in/out
idcnt := dcnt;
-- no acknowledge (until command complete)
ihost_ack := '0';
icore_txd := core_txd;
-- keep current command to i2c_core
icore_cmd := core_cmd;
-- no shifting or loading of shift-register
ishift := '0';
iload := '0';
-- keep current state;
nxt_state := state;
case state is
when st_idle =>
if (go = '1') then
if (start = '1') then
nxt_state := st_start;
icore_cmd := CMD_START;
elsif (read = '1') then
nxt_state := st_read;
icore_cmd := CMD_READ;
idcnt := "111";
else
nxt_state := st_write;
icore_cmd := CMD_WRITE;
idcnt := "111";
iload := '1';
end if;
end if;
when st_start =>
if (core_ack = '1') then
if (read = '1') then
nxt_state := st_read;
icore_cmd := CMD_READ;
idcnt := "111";
else
nxt_state := st_write;
icore_cmd := CMD_WRITE;
idcnt := "111";
iload := '1';
end if;
end if;
when st_write =>
if (core_ack = '1') then
idcnt := dcnt -1; -- count down Data_counter
icore_txd := sr(7);
if (dcnt = 0) then
nxt_state := st_ack;
icore_cmd := CMD_READ; -- for ack receive
else
ishift := '1';
-- icore_txd := sr(7);
end if;
end if;
when st_read =>
if (core_ack = '1') then
idcnt := dcnt -1; -- count down Data_counter
ishift := '1';
if (dcnt = 0) then
nxt_state := st_ack;
icore_cmd := CMD_WRITE;-- for ack send
icore_txd := ack_in;
end if;
end if;
when st_ack =>
if (core_ack = '1') then
-- generate command acknowledge signal
ihost_ack := '1';
-- Perform an additional shift, needed for 'read' (store last received bit in shift register)
ishift := '1';
-- check for stop; Should a STOP command be generated ?
if (stop = '1') then
nxt_state := st_stop;
icore_cmd := CMD_STOP;
else
nxt_state := st_idle;
icore_cmd := CMD_NOP;
end if;
end if;
when st_stop =>
if (core_ack = '1') then
nxt_state := st_idle;
icore_cmd := CMD_NOP;
end if;
when others => -- illegal states
nxt_state := st_idle;
icore_cmd := CMD_NOP;
end case;
-- generate registers
if (nReset = '0') then
core_cmd <= CMD_NOP;
core_txd <= '0';
shift <= '0';
ld <= '0';
dcnt <= "111";
host_ack <= '0';
state <= st_idle;
elsif (clk'event and clk = '1') then
if (ena = '1') then
state <= nxt_state;
dcnt <= idcnt;
shift <= ishift;
ld <= iload;
core_cmd <= icore_cmd;
core_txd <= icore_txd;
host_ack <= ihost_ack;
end if;
end if;
end process nxt_state_decoder;
end block statemachine;
end architecture structural;
--
--
-- I2C Core
--
-- Translate simple commands into SCL/SDA transitions
-- Each command has 5 states, A/B/C/D/idle
--
-- start: SCL ~~~~~~~~~~\____
-- SDA ~~~~~~~~\______
-- x | A | B | C | D | i
--
-- repstart SCL ____/~~~~\___
-- SDA __/~~~\______
-- x | A | B | C | D | i
--
-- stop SCL ____/~~~~~~~~
-- SDA ==\____/~~~~~
-- x | A | B | C | D | i
--
--- write SCL ____/~~~~\____
-- SDA ==X=========X=
-- x | A | B | C | D | i
--
--- read SCL ____/~~~~\____
-- SDA XXXX=====XXXX
-- x | A | B | C | D | i
--
-- Timing: Normal mode Fast mode
-----------------------------------------------------------------
-- Fscl 100KHz 400KHz
-- Th_scl 4.0us 0.6us High period of SCL
-- Tl_scl 4.7us 1.3us Low period of SCL
-- Tsu:sta 4.7us 0.6us setup time for a repeated start condition
-- Tsu:sto 4.0us 0.6us setup time for a stop conditon
-- Tbuf 4.7us 1.3us Bus free time between a stop and start condition
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
USE ieee.std_logic_unsigned.all;
entity i2c_core is
port (
clk : in std_logic;
nReset : in std_logic;
clk_cnt : in unsigned(7 downto 0); -- 4x SCL
cmd : in std_logic_vector(2 downto 0);
cmd_ack : out std_logic;
busy : out std_logic;
Din : in std_logic;
Dout : out std_logic;
SCL : inout std_logic;
SDA : inout std_logic
);
end entity i2c_core;
architecture structural of i2c_core is
constant CMD_NOP : std_logic_vector(2 downto 0) := "000";
constant CMD_START : std_logic_vector(2 downto 0) := "010";
constant CMD_STOP : std_logic_vector(2 downto 0) := "011";
constant CMD_READ : std_logic_vector(2 downto 0) := "100";
constant CMD_WRITE : std_logic_vector(2 downto 0) := "101";
type cmds is (idle, start_a, start_b, start_c, start_d, stop_a, stop_b, stop_c, rd_a, rd_b, rd_c, rd_d, wr_a, wr_b, wr_c, wr_d);
signal state : cmds;
signal SDAo, SCLo : std_logic;
signal txd : std_logic;
signal clk_en, slave_wait :std_logic;
signal cnt : unsigned(7 downto 0) := clk_cnt;
-- signal cnt : std_logic_vector(7 downto 0); -- := clk_cnt;
begin
-- whenever the slave is not ready it can delay the cycle by pulling SCL low
slave_wait <= '1' when ((SCLo = '1') and (SCL = '0')) else '0';
-- generate clk enable signal
gen_clken: process(clk, nReset)
begin
if (nReset = '0') then
cnt <= (others => '0');
clk_en <= '1'; --'0';
elsif (clk'event and clk = '1') then
if (cnt = 0) then
clk_en <= '1';
cnt <= clk_cnt;
else
if (slave_wait = '0') then
cnt <= cnt -1;
end if;
clk_en <= '0';
end if;
end if;
end process gen_clken;
-- generate statemachine
nxt_state_decoder : process (clk, nReset, state, cmd, SDA)
variable nxt_state : cmds;
variable icmd_ack, ibusy, store_sda : std_logic;
variable itxd : std_logic;
begin
nxt_state := state;
icmd_ack := '0'; -- default no acknowledge
ibusy := '1'; -- default busy
store_sda := '0';
itxd := txd;
case (state) is
-- idle
when idle =>
case cmd is
when CMD_START =>
nxt_state := start_a;
icmd_ack := '1'; -- command completed
when CMD_STOP =>
nxt_state := stop_a;
icmd_ack := '1'; -- command completed
when CMD_WRITE =>
nxt_state := wr_a;
icmd_ack := '1'; -- command completed
itxd := Din;
when CMD_READ =>
nxt_state := rd_a;
icmd_ack := '1'; -- command completed
when others =>
nxt_state := idle;
-- don't acknowledge NOP command icmd_ack := '1'; -- command completed
ibusy := '0';
end case;
-- start
when start_a =>
nxt_state := start_b;
when start_b =>
nxt_state := start_c;
when start_c =>
nxt_state := start_d;
when start_d =>
nxt_state := idle;
ibusy := '0'; -- not busy when idle
-- stop
when stop_a =>
nxt_state := stop_b;
when stop_b =>
nxt_state := stop_c;
when stop_c =>
-- nxt_state := stop_d;
-- when stop_d =>
nxt_state := idle;
ibusy := '0'; -- not busy when idle
-- read
when rd_a =>
nxt_state := rd_b;
when rd_b =>
nxt_state := rd_c;
when rd_c =>
nxt_state := rd_d;
store_sda := '1';
when rd_d =>
nxt_state := idle;
ibusy := '0'; -- not busy when idle
-- write
when wr_a =>
nxt_state := wr_b;
when wr_b =>
nxt_state := wr_c;
when wr_c =>
nxt_state := wr_d;
when wr_d =>
nxt_state := idle;
ibusy := '0'; -- not busy when idle
end case;
-- generate regs
if (nReset = '0') then
state <= idle;
cmd_ack <= '0';
busy <= '0';
txd <= '0';
Dout <= '0';
elsif (clk'event and clk = '1') then
if (clk_en = '1') then
state <= nxt_state;
busy <= ibusy;
txd <= itxd;
if (store_sda = '1') then
Dout <= SDA;
end if;
end if;
cmd_ack <= icmd_ack and clk_en;
end if;
end process nxt_state_decoder;
--
-- convert states to SCL and SDA signals
--
output_decoder: process (clk, nReset, state)
variable iscl, isda : std_logic;
begin
case (state) is
when idle =>
iscl := SCLo; -- keep SCL in same state
isda := SDA; -- keep SDA in same state
-- start
when start_a =>
iscl := SCLo; -- keep SCL in same state (for repeated start)
isda := '1'; -- set SDA high
when start_b =>
iscl := '1'; -- set SCL high
isda := '1'; -- keep SDA high
when start_c =>
iscl := '1'; -- keep SCL high
isda := '0'; -- sel SDA low
when start_d =>
iscl := '0'; -- set SCL low
isda := '0'; -- keep SDA low
-- stop
when stop_a =>
iscl := '0'; -- keep SCL disabled
isda := '0'; -- set SDA low
when stop_b =>
iscl := '1'; -- set SCL high
isda := '0'; -- keep SDA low
when stop_c =>
iscl := '1'; -- keep SCL high
isda := '1'; -- set SDA high
-- write
when wr_a =>
iscl := '0'; -- keep SCL low
-- isda := txd; -- set SDA
isda := Din;
when wr_b =>
iscl := '1'; -- set SCL high
-- isda := txd; -- set SDA
isda := Din;
when wr_c =>
iscl := '1'; -- keep SCL high
-- isda := txd; -- set SDA
isda := Din;
when wr_d =>
iscl := '0'; -- set SCL low
-- isda := txd; -- set SDA
isda := Din;
-- read
when rd_a =>
iscl := '0'; -- keep SCL low
isda := '1'; -- tri-state SDA
when rd_b =>
iscl := '1'; -- set SCL high
isda := '1'; -- tri-state SDA
when rd_c =>
iscl := '1'; -- keep SCL high
isda := '1'; -- tri-state SDA
when rd_d =>
iscl := '0'; -- set SCL low
isda := '1'; -- tri-state SDA
end case;
-- generate registers
if (nReset = '0') then
SCLo <= '1';
SDAo <= '1';
elsif (clk'event and clk = '1') then
if (clk_en = '1') then
SCLo <= iscl;
SDAo <= isda;
end if;
end if;
end process output_decoder;
SCL <= '0' when (SCLo = '0') else 'Z'; -- since SCL is externally pulled-up convert a '1' to a 'Z'(tri-state)
SDA <= '0' when (SDAo = '0') else 'Z'; -- since SDA is externally pulled-up convert a '1' to a 'Z'(tri-state)
-- SCL <= SCLo;
-- SDA <= SDAo;
end architecture structural;
-- Listing 5.3 : Pong P. Chu
-- FPGA Prototyping by VHDL Examples
-- Wiley 2008
library ieee;
use ieee.std_logic_1164.all;
entity edge_detect is
port(
clk, reset: in std_logic;
level: in 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';
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 =>
if level= '0' then
state_next <= zero;
end if;
end case;
end process;
end moore_arch;
Programme d'utilisation
[modifier | modifier le wikicode]Réaliser un programme capable de lire la manette Nunchuk.
Nous avons laissé les sous-programmes utiles pour une utilisation de la liaison série. En ce qui nous concerne, nous les avons utilisés pour déboguer le périphérique.
#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 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);
// 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();
//usart_init();
//usart_send('0');usart_send(13);usart_send(10);
//usart_puts("TWBR=");usart_puts_hexa(TWBR);usart_send(13);usart_send(10);
_delay_us(500);
TWIStartWrite(WII_NUNCHUK_I2C_ADDRESS<<1);
TWIWrite(0xF0);
TWIWriteStop(0x55);
_delay_us(100);
TWIStartWrite(WII_NUNCHUK_I2C_ADDRESS<<1);
TWIWrite(0xFB);
TWIWriteStop(0x00);
_delay_ms(10);
// loop
while(1) {
// reinitialisation pour lecture
TWIStartWrite(WII_NUNCHUK_I2C_ADDRESS<<1);
TWIWriteStop(0x00);
_delay_ms(2);
TWIStartWrite((WII_NUNCHUK_I2C_ADDRESS<<1)|1);
for (i=0;i<5;i++)
t[i] = TWIReadACK();
t[5] = TWIReadNACKStop();
PORTC = t[2];
_delay_ms(100);
}
return 0;
}
void TWIInit(void)
{
//set SCL to {{unité|200|kHz}} TWBR=Fcpu/(5*Fscl)
TWBR = 25;
//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;
}
//************************************************************************
// 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);
}
}
Voir aussi
[modifier | modifier le wikicode]- Dans la partie travaux pratiques : Utiliser l'i2c avec d'autres capteurs
Problèmes matériels résolus avec ce soft processeur
[modifier | modifier le wikicode]Nous avons découvert depuis un certain temps que les calculs en nombre flottant posent des problèmes à notre cœur ATMega16.
Il y a probablement une instruction ou plusieurs instructions qui ne fonctionnent pas correctement.
Cette section est destinée à contenir l'histoire de la résolution de ce problème. Il s'agit d'une enquête minutieuse dans laquelle nous avons fait des erreurs : considérer après un test une instruction comme bonne alors qu'elle ne l'était pas !
Pour réaliser cette recherche nous avons d'abord essayé de programmer une addition de nombres flottants entièrement en c en espérant y retrouver le problème. Pourquoi cette façon de procéder ? Parce qu'autrement il faut lire entièrement la librairie de calcul flottant et y débusquer une instruction qui n'a jamais été utilisée par nos autres différents programmes. Le meilleurs moyen de réaliser cela de manière automatique aurait été de réaliser un pseudo-assembleur capable de lire les fichiers .lss et de lister toutes les instructions assembleur utilisées. En comparant les listes avec et sans calcul flottant on pouvait espérer trouver la ou les instructions coupables. Ce travail n'a tout simplement pas été réalisé malgré notre connaissance très (trop) ancienne sur la réalisation de compilateur avec les outils Lex et Yacc. Pour le problème posé, seule l'utilisation de "lex" ou plutôt "Flex" aurait été nécessaire.
Commençons par réaliser un travail sur les additions avec un compilateur standard pour PC, à savoir le GNU C. Notre objectif sera ensuite de porter ce travail dans notre SOC ATMega16 pour examiner son comportement.
Addition flottante en C sur PC
[modifier | modifier le wikicode]Il est beaucoup plus facile de travailler sur un PC que sur un microcontrôleur car nous rencontrons beaucoup moins de problème d'entrées/sorties.
Voici donc un programme d'addition de nombre flottants en C destiné à un PC. Nous sommes parti d'un travail de Mike Field Custom floating point mais l'avons complètement modifié.
#include <stdio.h>
typedef union {
float f_temp; //taille : {{unité|4|octets}}
unsigned int li_temp; //taille : {{unité|4|octets}}
} t_u;
/****************************************************************/
/* Adjust so MSB of mantissa is set */
/****************************************************************/
static void fixed_normalize(unsigned *m, signed *e) {
/* Adjust to have a one in the MSB of mantissa */
if(*m == 0) {
*e = 0;
return;
}
while ((*m & 0xF800000)!=0) {
*m >>= 1;
*e = *e +1;
}
// on a été trop loin on redécale et on efface un bit
*e = *e - 1;
*m <<= 1;
*m &= ~0x800000; // effacement du bit de poids fort qui n'est pas present
}
/****************************************************************/
/* Add two numbers together */
/****************************************************************/
void fixed_add(unsigned int *m1, unsigned int *e1, unsigned int *m2, unsigned int *e2) {
if(*m2 == 0) {
return; /* Nothing to add */
}
if(*m1 == 0) {
*m1 = *m2;
*e1 = *e2;
return; /* Nothing in m1 at the moment */
}
/* Do we need to move the decimal on m1*2^e1 ? */
while(*e2 > *e1) {
*m1 >>= 1;
*e1 = *e1 + 1;
}
/* Do we need to move the decimal on m2*2^e2 ? */
while(*e1 > *e2) {
*m2 >>= 1;
*e2 = *e2 + 1;
}
/* As e2 = e1 we can now just add */
*m1 += *m2;
/* Test for overflow */
// We will never encounter an overflow with 24 bits in 32-bits numbers !!!!
if(*m1 < *m2) {
*m1 = (*m1>>1) | 0x80000000;
*e1 = *e1 + 1;
}
}
/****************************************************************/
/* Conversion for printing */
/****************************************************************/
static float fixed_to_float(unsigned int m, unsigned int e) {
float t;
t_u t_v;
//t_v.f_temp = m;
t_v.li_temp = e;
t_v.li_temp <<=23;
t_v.li_temp |= (m & 0x007FFFFF);
return t_v.f_temp;
}
int main() {
float nb1,nb2,somme;
unsigned int m1,m2,e1,e2;
//int e1,e2;
t_u t_v1,t_v2;
printf("Entrez deux nombres flotants : ");
scanf ("%f %f", &nb1,&nb2);
t_v1.f_temp = nb1;
t_v2.f_temp = nb2;
printf("Deux nombres initiaux : %x %x\n",t_v1.li_temp,t_v2.li_temp);
// recupération de l'exposant
e1 = (t_v1.li_temp>>23) & 0x000000FF;// sans le signe
// recuperation mantisse
m1 = (t_v1.li_temp & 0X007FFFFF);
// mise à 1 du bit manquant
m1 |= (1<<23);
e2 = (t_v2.li_temp>>23) & 0x000000FF;// sans le signe
// recuperation mantisse
m2 = (t_v2.li_temp & 0X007FFFFF);
// mise à 1 du bit manquant
m2 |= (1<<23);
printf("%x %x - %x %x\n",m1,e1,m2,e2);
fixed_add(&m1, &e1, &m2, &e2);
printf("avant normalisation : %x %x\n",m1,e1);
fixed_normalize(&m1, &e1);
printf("apres normalisation : %x %x\n",m1,e1);
somme = fixed_to_float(m1, e1);
printf("Le resultat est %f à comparer a %f\n",somme,nb1+nb2);
t_v1.f_temp = somme;
printf("Le resultat HEXA : %x\n",t_v1.li_temp);
return 0;
}
Ce programme donne :
serge@Rosetta:~/Xilinx/Nexys3/Atmega16i2c/soft$ ./essai Entrez deux nombres flotants : 1.5 1.25 Deux nombres initiaux : 3fc00000 3fa00000 c00000 7f - a00000 7f avant normalisation : 1600000 7f apres normalisation : 300000 80 Le resultat est 2.750000 à comparer a 2.750000 Le resultat HEXA : 40300000
mais n'est pas prévu pour fonctionner avec des nombres négatifs. Ce qui est donné ici nécessite une grande connaissance du format flottant et de l'hexadécimal correspondant si on veut en faire une analyse fine.
Addition flottante en C sur notre SOC ATMega16
[modifier | modifier le wikicode]Puisque le problème d'une addition complètement écrite en c est résolu en section précédente, nous allons tenter de porter ce code dans notre ATMega16 et comparer cette addition à celle de la librairie standard du C.
#include <avr/io.h>
//#include <avr/interrupt.h>
#undef F_CPU
#define F_CPU 50000000UL
#include "util/delay.h"
#include <stdio.h>
typedef union {
float f_temp;
uint32_t li_temp;
} t_u;
// 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);
/****************************************************************/
/* Add two numbers together */
/****************************************************************/
void fixed_add(uint32_t *m1, uint16_t *e1, uint32_t *m2, uint16_t *e2) {
if(*m2 == 0) {
return; /* Nothing to add */
}
if(*m1 == 0) {
*m1 = *m2;
*e1 = *e2;
return; /* Nothing in m1 at the moment */
}
/* Do we need to move the decimal on m1*2^e1 ? */
while(*e2 > *e1) {
*m1 >>= 1;
*e1 = *e1 + 1;
}
/* Do we need to move the decimal on m2*2^e2 ? */
while(*e1 > *e2) {
*m2 >>= 1;
*e2 = *e2 + 1;
}
/* As e2 = e1 we can now just add */
*m1 += *m2;
/* Test for overflow */
// We will never encounter an overflow with 24 bits in 32-bits numbers !!!!
if(*m1 < *m2) {
*m1 = (*m1>>1) | 0x80000000;
*e1 = *e1 + 1;
}
}
/****************************************************************/
/* Conversion for printing */
/****************************************************************/
static float fixed_to_float(uint32_t m, uint16_t e) {
//float t;
t_u t_v;
//t_v.f_temp = m;
t_v.li_temp = e;
t_v.li_temp <<=23;
t_v.li_temp |= (m & 0x007FFFFF);
return t_v.f_temp;
}
/****************************************************************/
/* Adjust so MSB of mantissa is set */
/****************************************************************/
static void fixed_normalize(uint32_t *m, uint12_t *e) {
/* Adjust to have a one in the MSB of mantissa */
if(*m == 0) {
*e = 0;
return;
}
while ((*m & 0xF800000)!=0) {
*m >>= 1;
*e = *e +1;
}
// on a été trop loin on redécale et on efface un bit
*e = *e - 1;
*m <<= 1;
*m &= ~0x800000; // effacement du bit de poids fort qui n'est pas present
}
/************* main pour recherche Pb dans instructions *************/
int main() {
char str[10];
t_u fnb1,fnb2,somme;
uint32_t m1,m2;
uint16_t e1,e2;
// init
usart_init();
usart_puts("DEBUT");usart_send(13);usart_send(10);
_delay_ms(3000);
// loop
while(1) {
fnb1.li_temp=0x3F800000; //=1.0
fnb1.li_temp|=0x00400000; //=1.5 maintenant
fnb2.li_temp=0x3F800000; //=1.0
//fnb2.li_temp|=0x00400000; //=1.5 maintenant
fnb2.li_temp|=0x00200000; //=1.25 maintenant
// recupération de l'exposant
e1 = (fnb1.li_temp>>23) & 0x000000FF;// sans le signe
// recuperation mantisse
m1 = (fnb1.li_temp & 0X007FFFFF);
// mise à 1 du bit manquant
m1 |= ((uint32_t) 1<<23);
usart_puts_hexa(m1>>16);usart_puts_hexa(m1);usart_puts(" - ");usart_puts_hexa(e1); usart_send(13);usart_send(10);
// recupération de l'exposant
e2 = (fnb2.li_temp>>23) & 0x000000FF;// sans le signe
// recuperation mantisse
m2 = (fnb2.li_temp & 0X007FFFFF);
// mise à 1 du bit manquant
m2 |= ((uint32_t) 1<<23);
usart_puts_hexa(m2>>16);usart_puts_hexa(m2);usart_puts(" - ");usart_puts_hexa(e2); usart_send(13);usart_send(10);
fixed_add(&m1, &e1, &m2, &e2);
usart_puts("sum = ");usart_puts_hexa(m1>>16);usart_puts_hexa(m1);usart_puts(" - ");usart_puts_hexa(e1); usart_send(13);usart_send(10);
fixed_normalize(&m1, &e1);
usart_puts("sum norm = ");usart_puts_hexa(m1>>16);usart_puts_hexa(m1);usart_puts(" - ");
usart_puts_hexa(e1); usart_send(13);usart_send(10);
somme.f_temp = fixed_to_float(m1, e1);
usart_puts("sum = ");usart_puts_hexa(somme.li_temp>>16);
usart_puts_hexa(somme.li_temp);usart_send(13);usart_send(10);
somme.f_temp = fnb1.f_temp + fnb1.f_temp;
usart_puts("sum (lib) = ");usart_puts_hexa(somme.li_temp>>16);
usart_puts_hexa(somme.li_temp);usart_send(13);usart_send(10);
_delay_ms(1000);
}
return 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
// 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);
}
}
L'exécution de ce programme donne dans un moniteur série :
00C00000 - 007F 00A00000 - 007F sum = 01600000 - 007F sum norm = 00300000 - 0080 sum = 40300000 sum (lib) = 40400000
Si vous comparez les deux sections (celle-ci et la précédente) vous voyez que sur AVR tout se passe bien jusqu'à "sum" de l'avant dernière ligne. C'est-à-dire avec notre propre routine d'addition. Par contre avec "sum (lib)" qui est un calcul utilisant la librairie standard d' avr-gcc, on ne trouve pas exactement ce que l'on devrait trouver.
Résultats de l'enquête
[modifier | modifier le wikicode]Histoire de la recherche
[modifier | modifier le wikicode]- Le point essentiel du code des deux sections précédentes est la définition d'une union qui permet de voir un même nombre de 32 bits soit comme un nombre flottant, soit comme un nombre entier (hexadécimal) :
typedef union {
float f_temp;
uint32_t li_temp;
} t_u;
- On s'est relativement très vite aperçu que si l'on voulait faire une addition il fallait en fait faire une soustraction. En clair, si dans un programme dans mon AVR j'écrit : "sum = nb1-nb2;" j’obtiens dans sum la valeur nb1+nb2.
- Quand on sait que le signe d'un nombre flottant est donné par le bit de poids fort, nous avons essayé de changer le signe du nombre flottant avec une instruction c :
fnb1.li_temp=0x3F800000; //=1.0
fnb1.li_temp|=0x00400000; //=1.5 maintenant
fnb1.li_temp|=0x80000000; //=-1.5 maintenant : changement de signe ici
Le changement de signe de la troisième ligne était réalisé par le programme assembleur (c compilé) :
set bld r16,7
- Nous avions classé l'instruction BLD parmi les instructions fonctionnant correctement mais ce code assembleur ne fonctionnait pas correctement. Il donnait la valeur hexadécimale 0x808400000 au lieu de la valeur 0xBF8400000 attendue.
- Nous venions de découvrir la première instruction fautive : BLD.
- si r16 = XXXXXXXX bld r16,7 doit donner r16 = TXXXXXXX (T est un flag qui est mis à 1 par l'instruction "set" qui précède)
- si r16 = XXXXXXXX notre bld r16,7 donnait r16 = T0000000 (les autres bits du registre étaient perdus)
- la correction de cette instruction est donnée un peu plus loin. Nous pensions qu'immédiatement le calcul flottant fonctionnerait correctement mais ce n'était pas le cas !!!
- Une autre instruction coupable était à chercher. Cette recherche a été parasitée par la découverte des instructions CBI et SBI ne fonctionnant pas correctement. Elles ne pouvaient absolument pas être coupables du dysfonctionnement de la librairie flottante car elles servent à mettre un bit à 1 ou à 0 dans les registres I/O (pas dans les registres r0,...,r31, seuls utilisés dans la librairie) !
- le TRUC c'est que pour faire fonctionner ces CBI et SBI il fallait configurer l'ALU dans le même mode que l'instruction contraire de BLD (notre premier coupable) à savoir BST. Et celle-là n'avait encore jamais été testée et elle apparaissait une fois dans la librairie d'addition flottante.
- Nous nous sommes vite aperçu que BST ne fonctionnait pas correctement non plus avec un programme assembleur du genre :
ldi r16,0xF0 bst r16,4 ; devait mettre 1 dans T mais mettait 0 ! ;bst r16,3 ; devait mettre 0 dans T mais mettait 1 ! bld r16,0 ; recopie de T en b0 out PORTC,r16 ;n'allumait pas le bit b0 du PORTC
- Puisque nous avons mis en cause les instructions CBI et SBI, il nous en faut dire un petit mot ici. Ces instructions fonctionnent en fait parfaitement à condition que la sortie des registres d' I/O soit bouclée sur l'entrée correspondante, ce que nous ne faisons pas toujours. Ce problème est déjà abordé ICI dans ce chapitre.
Correction du code de l'ATMega16
[modifier | modifier le wikicode]- Correction de l'instruction BLD
Il faut aller dans le fichier VHDL : alu.vhd en ligne 188 (ou pas loin pour vous). Vous y trouverez le code :
when ALU_BLD => -- copy T flag to DOUT
case I_BIT(2 downto 0) is
when "000" => L_DOUT( 0) <= I_FLAGS(6);
L_DOUT( 8) <= I_FLAGS(6);
when "001" => L_DOUT( 1) <= I_FLAGS(6);
L_DOUT( 9) <= I_FLAGS(6);
when "010" => L_DOUT( 2) <= I_FLAGS(6);
L_DOUT(10) <= I_FLAGS(6);
when "011" => L_DOUT( 3) <= I_FLAGS(6);
L_DOUT(11) <= I_FLAGS(6);
when "100" => L_DOUT( 4) <= I_FLAGS(6);
L_DOUT(12) <= I_FLAGS(6);
when "101" => L_DOUT( 5) <= I_FLAGS(6);
L_DOUT(13) <= I_FLAGS(6);
when "110" => L_DOUT( 6) <= I_FLAGS(6);
L_DOUT(14) <= I_FLAGS(6);
when others => L_DOUT( 7) <= I_FLAGS(6);
L_DOUT(15) <= I_FLAGS(6);
end case;
et vous devez le remplacer par :
when ALU_BLD => -- copy T flag to DOUT
--Q_FLAGS(6) <= I_FLAGS(6);-- done by default far all flags
case I_BIT(2 downto 0) is
when "000" => L_DOUT(0) <= I_FLAGS(6);
L_DOUT(7 downto 1) <= I_DIN(7 downto 1);
L_DOUT(8) <= I_FLAGS(6);
L_DOUT(15 downto 9) <= I_DIN(7 downto 1);
when "001" => L_DOUT(1) <= I_FLAGS(6);
L_DOUT(0) <= I_DIN(0);
L_DOUT(7 downto 2) <= I_DIN(7 downto 2);
L_DOUT(9) <= I_FLAGS(6);
L_DOUT(8) <= I_DIN(0);
L_DOUT(15 downto 10) <= I_DIN(7 downto 2);
when "010" => L_DOUT( 2) <= I_FLAGS(6);
L_DOUT(1 DOWNTO 0) <= I_DIN(1 DOWNTO 0);
L_DOUT(7 downto 3) <= I_DIN(7 downto 3);
L_DOUT(10) <= I_FLAGS(6);
L_DOUT(9 DOWNTO 8) <= I_DIN(1 DOWNTO 0);
L_DOUT(15 downto 11) <= I_DIN(7 downto 3);
when "011" => L_DOUT( 3) <= I_FLAGS(6);
L_DOUT(2 DOWNTO 0) <= I_DIN(2 DOWNTO 0);
L_DOUT(7 downto 4) <= I_DIN(7 downto 4);
L_DOUT(11) <= I_FLAGS(6);
L_DOUT(10 DOWNTO 8) <= I_DIN(2 DOWNTO 0);
L_DOUT(15 downto 12) <= I_DIN(7 downto 4);
when "100" => L_DOUT( 4) <= I_FLAGS(6);
L_DOUT(3 DOWNTO 0) <= I_DIN(3 DOWNTO 0);
L_DOUT(7 downto 5) <= I_DIN(7 downto 5);
L_DOUT(12) <= I_FLAGS(6);
L_DOUT(11 DOWNTO 8) <= I_DIN(3 DOWNTO 0);
L_DOUT(15 downto 13) <= I_DIN(7 downto 5);
when "101" => L_DOUT( 5) <= I_FLAGS(6);
L_DOUT(4 DOWNTO 0) <= I_DIN(4 DOWNTO 0);
L_DOUT(7 downto 6) <= I_DIN(7 downto 6);
L_DOUT(13) <= I_FLAGS(6);
L_DOUT(12 DOWNTO 8) <= I_DIN(4 DOWNTO 0);
L_DOUT(15 downto 14) <= I_DIN(7 downto 6);
when "110" => L_DOUT( 6) <= I_FLAGS(6);
L_DOUT(5 DOWNTO 0) <= I_DIN(5 DOWNTO 0);
L_DOUT(7) <= I_DIN(7);
L_DOUT(14) <= I_FLAGS(6);
L_DOUT(13 DOWNTO 8) <= I_DIN(5 DOWNTO 0);
L_DOUT(15) <= I_DIN(7);
when others => L_DOUT( 7) <= I_FLAGS(6);
L_DOUT( 6 downto 0) <= I_DIN(6 downto 0);
L_DOUT(15) <= I_FLAGS(6);
L_DOUT( 14 downto 8) <= I_DIN(6 downto 0);
end case;
- Correction de l'instruction BST
La correction de cette instruction est beaucoup plus simple. Il faut encore aller dans le fichier VHDL : alu.vhd en ligne 237 (ou pas loin pour vous si vous avez fait la correction précédente). Vous y trouverez le code :
when ALU_BIT_CS => -- copy I_DIN to T flag
Q_FLAGS(6) <= L_RBIT xor not I_BIT(3);
et vous devez le remplacer par :
when ALU_BIT_CS => -- copy I_DIN to T flag
Q_FLAGS(6) <= L_RBIT; --removed 20/10/2020 : xor not I_BIT(3);
Et vous êtes maintenant capable de calculer un cosinus ou un sinus en nombre flottant.
Avant de commencer d'envisager les changements de cette section dans votre projet, regardez la date de votre fichier alu.vhd. Si elle est du 20 octobre 2020 (ou après), cette correction a déjà été réalisée.
Avancées technologiques 2019
[modifier | modifier le wikicode]Ce que nous avons réalisé dans ce chapitre peut être trouvé commercialement maintenant (2019 et probablement depuis quelques années). Nous voulons dire quelque chose à peu près équivalent mais plus facile à utiliser (et encore un peu cher à notre goût). Présentons d'abord un produit de Alorium Technology.
Au moment où nous écrivons ces lignes nous n'avons pas testé les produits présentés dans cette section. Mais il ne fait aucun doute que nous allons nous y intéresser assez rapidement.
XLR8 : FPGA Intel MAX 10
[modifier | modifier le wikicode]XLR8 est une fusion parfaite entre les vitesses des FPGA et la plateforme Arduino connue de presque tous. Cette carte vous donne accès à l'ensemble des boucliers Arduino. Des adaptateurs 5V/3.3V sont présents pour une compatibilité 5V. La mise en service ne nécessite pas de connaissances particulières en FPGA puisqu'il s'agit simplement de programmer un SOC avec la librairie Arduino.
Ce qui fait la différence avec un Arduino c'est la possibilité d'utiliser des XBs (pre-programmed hardware Xcelerator Blocks) des blocs enfouis dans le FPGA pour des fonctionnalités diverses comme :
- le contrôle de servomoteurs
- l'accélération du calcul flottant
- la conversion analogique numérique étendue
- le contrôle NeoPixel
- la gestion des quadratures
Il est d'autre part possible de gérer ses propres fabrications de XBs et c'est là naturellement qu'intervient le FPGA et toutes les connaissances particulières qui lui sont associées.
Voir aussi
[modifier | modifier le wikicode]- Carte XLR8 avec Intel max 10
- Carte SNO une mini carte de Alorium Technology
- Tous le produits Alorium Technology
- Top 7 Reasons to Replace Your Microcontroller with a MAX® 10 FPGA (Intel)
Alorium Technology a basé sa technologie sur les AVR 8 bits. Arduino a fait un autre pari : le 32 bits avec le MKR Vidor 4000.
Le MKR Vidor 4000 (Arduino)
[modifier | modifier le wikicode]Dans ce produit apparu en 2019, le processeur est en dur et externe au FPGA
Avec le MKR VIDOR 4000 vous pouvez le configurer de la façon que vous voulez. Il inclut l'interface classique MKR qui permet aux broches d'être reliée aux deux, le SAMD21 et le FPGA.
Le FPGA contient 16K Logic Elements, 504 KB de RAM, et 56 18x18 bit multiplieurs harware pour DSP grande vitesse. Chaque sortie peut basculer jusqu'à 150 MHz et peut être configurée pour des fonctions telles qu'UARTs, (Q)SPI, grande résolution haute fréquence PWM, encodeur de quadrature, I2C, I2S, Sigma Delta DAC, etc.
- Page de présentation du MKR Vidor 4000 en anglais
- arduino mkr vidor 4000 présentation et mise en route en français.
- mkr vidor 4000 programmation du fpga partie-1 en français.
- mkr vidor 4000 programmation du fpga partie 2 en français.
- Vidor FPGA (github)