Very High Speed Integrated Circuit Hardware Description Language/Travail pratique/Autres projets pour ATMEL ATMega8

Début de la boite de navigation du travail pratique


Nous avons l'intention de mettre dans ce chapitre une série de projets permettant de faire évoluer et de mettre en œuvre le coreATMega8. Il a été conçu par Juergen Sauermann.

Autres projets pour ATMEL ATMega8
Image logo représentative de la faculté
T.P. no 6
Leçon : Very High Speed Integrated Circuit Hardware Description Language

TP de niveau 15.

Précédent :TP 5
Suivant :Projets Hardware pour ATMEL ATMega16
En raison de limitations techniques, la typographie souhaitable du titre, « Travail pratique : Autres projets pour ATMEL ATMega8
Very High Speed Integrated Circuit Hardware Description Language/Travail pratique/Autres projets pour ATMEL ATMega8
 », n'a pu être restituée correctement ci-dessus.
Panneau d’avertissement L'hébergement de mon site perso se termine le 5 septembre 2023 ! A ce jour je n'ai pas encore décidé comment je vais gérer ce problème dont je ne suis pas à l'origine. Il en résulte que l'ensemble des corrections qui utilisent mon site perso seront indisponibles à partir de cette date pour tout ce chapitre. SergeMoutou (discuter)


Projet pour l'année universitaire 2011/2012

modifier

Étant satisfait avec le coreATMega8, nous avons décidé de poursuivre l’utilisation de ce système monopuce, et de l’utiliser pour réaliser un autre projet. La carte cible sera plus la même, elle sera réalisée autour d'un spartan3 200. Étant donné que les corrections du jeu de Pong ont été complètement publiées dans un autre chapitre, il nous faut changer de sujet. Il s'agit d'un jeu un peu similaire au jeu de Pong, à savoir un Casse-briques.

Version de l'ATMega8

modifier

L'écriture du chapitre Programmer in Situ et déboguer nous a obligé à faire évoluer le cœur embarqué coreATMega8.


Cahier des charges

modifier

Une seule raquette est mobile verticalement. L'autre est présente mais son déplacement est lié à celui de la balle. Donc le rebond sur cette raquette se fait toujours.

 
Écran VGA définissant notre casse brique

Nous présentons ci-dessus l'écran VGA pour le casse-brique dans l'hypothèse où le déplacement de la raquette liée à la balle est fait par le processeur. Si l’on compare au jeu de Pong du chapitre sur l'ATMega8, il faut 8 bits supplémentaire pour gérer ce nouveau jeu.

Travail à réaliser

modifier

Trois phases distinctes sont à réaliser, un projet tuteuré sur environ 12 heures qui se solde par un rapport, la réalisation de la partie matérielle à partir de l'existant (c'est-à-dire le projet de pong précédent), puis le développement de la partie logicielle.

Projet tuteuré

modifier

Réaliser un schéma fonctionnel complet de la partie matérielle. Nous aimerions un schéma réalisé sous OpenOffice pour pouvoir le publier ici. Calculer le nombre de PORTs nécessaires pour interfacer votre écran VGA et votre processeur. À ce stade, il vous faut choisir si la liaison entre la balle et la raquette se fait à l'aide du matériel où à l'aide du logiciel.

Apprentissage du GNU C pour l'AVR. Pour cela vous partirez du répertoire /AVRComplet16_S4_S4 du fichier ATMega8_pong_VGA.zip qui contient une version améliorée du processeur, capable de gérer les interruptions et surtout capable d'exécuter une instruction qui écrit en programme mémoire. Cela permet de réaliser un bootloader ce qui facilite grandement le développement. Nous nous contenterons d’utiliser « data2mem » pour cette année car nous n'avons pas réussi jusqu'à présent à faire fonctionner notre bootloader.

La partie matérielle gère les quatre afficheurs sept segments et les LEDs à travers des PORTs :

  • PORTB pour l’affichage des 7 segments
  • PORTC pour les 8 LEDs
  • PORTD pour la sélection des digits d'affichages

Écrire des programmes C réalisant des chenillards un compteur sur deux digits.

Corrections
modifier

Voici un programme C réalisant un chenillard :

#include "avr/io.h"

#undef F_CPU
#define F_CPU 25000000UL
#include "util/delay.h"

main(void) 
{ // La gestion de DDRB et DDRC est inutile pour ce cœur
// DDRB = 0xFF; // 8 sorties pour B retiré car non implanté (voir ci-dessus)
// DDRC = 0x00; // 8 entrees pour C retiré car non implanté
  uint8_t i=0;
  while(1) {
    i++;
    i &= 0x07; 
    PORTC = 1 << i; 
    _delay_ms(1000);
  }
}

Partie matérielle

modifier

Le chapitre Interface VGA et PS2 ou le répertoire /CorrProjet2010 du fichier ATMega8_pong_VGA.zip contiennent une correction de la partie VGA du jeu de Pong (fichier VGA_top.vhd et ses composants) Modifier ce fichier pour remplacer l’affichage du score inutile par deux rangées de 8 briques au milieu de l'écran. Il serait bon de changer la place de l’affichage des scores (plus au milieu). Pour mettre au point cette partie on travaillera sans processeur. Une fois que tout est au point on crée l'interface entre cette partie et le processeur.

Pour aider les étudiants, nous proposons les choix technologiques suivants reliant les PORTs aux signaux de commande VGA :

choix technologiques
E/S module VGA E/S AVR
x_rect<9:8> PORTD<1:0>
x_rect<7:0> DDRD<7:0>
y_rect<9:8> PORTB<1:0>
y_rect<7:0> DDRB<7:0>
y_raqG<7:0> PORTC<7:0>
y_raqD<7:0> DDRC<7:0>
ligne1<7:0> PORTA<7:0>
ligne2<7:0> DDRA<7:0>
scoreG<7:0> EEDR<7:0>

Le registre EEDR est normalement réservé aux données de l'EEPROM. Comme nous n'avons pas d'EEPROM dans notre cœur embarqué nous l'utilisons pour ajouter 8 bits de sorties supplémentaires. « ligne1 » et « ligne2 » gèrent le 1er mur1 et le 2eme mur de l'image ci-dessus.

C'est une caractéristique de ce cœur, cette facilité à transformer des registres en PORTs. Nous rappelons que ceci est fait avec le code VHDL :

   -- ceci est dans un fichier io2.vhd qui remplace io.vhd original
   -- 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 X"31" => Balle_xLow <= I_DIN; --DDRD
                   when X"32"  => Balle_xHigh <= I_DIN;  --PORTD
                   when X"37"  => Balle_yLow <= I_DIN;  --DDRB
	 when X"38" => Balle_yHigh <= I_DIN; --PORTB
	 when X"34" => raqD_y <= I_DIN; --DDRC
	 when X"35" => raqG_y <= I_DIN; --PORTC
	 when X"3B" => s_ligne1 <= I_DIN; -- PORTA
	 when X"3A" => s_ligne2 <= I_DIN; -- DDRA
	 when X"3D" => s_scoreg <= I_DIN; -- EEDR : EEPROM Data Register
                   when X"40"  =>  -- 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;

Et voici de manière schématique ce qu’il faut faire pour interfacer notre écran VGA au processeur :

 
Travail à réaliser pour une connexion
Début d’un principe
Fin du principe


  • Le composant VGATop est inclu dans le fichier io.vhd qui est renommé io2.vhd pour l’occasion.
  • La connexion à l'intérieur de io2.vhd se fait avec le process vhdl qui est montré plus haut dans cette section.

Solution

modifier

Exercice3 question 3 du chapitre Interfaces VGA et PS2 contient une correction de la partie gestion de l'écran VGA.


Partie logicielle

modifier

Programmer l’ensemble avec plusieurs niveaux de jeu :

  • un premier niveau avec une rangée de briques pour lequel n’importe quel rebond sur une brique éteint cette brique
  • un deuxième niveau avec une rangée de brique avec une sur deux d'allumée pour lequel n’importe quel rebond sur une brique éteint cette brique
  • un troisième niveau semblable au premier niveau avec une rangée de briques mais pour lequel seule une balle provenant de votre raquette pourra éteindre une brique
  • un quatrième niveau semblable au deuxième niveau avec une rangée de briques une sur deux allumée mais pour lequel seule une balle provenant de votre raquette éteindra une brique
  • un cinquième de niveaux avec deux rangées de briques.

Les étudiants ont toute latitude pour accélérer les balles et trouver un algorithme de calcul des scores.

Le changement de pente pour la balle pourra se faire de manière aléatoire par la raquette qui suit la balle.

Conclusion

modifier

Ce travail a été donné à deux étudiants pour 60 heures de projet. La partie matérielle a été réalisée en 30 heures, comme prévu à l'origine. Nous rappelons qu’il s'agissait de partir de la partie matérielle du jeu de Pong et de la modifier pour le casse briques.

La partie logicielle qu’ils ont développé gérait :

  • les rebonds sur les deux raquettes
  • les rebonds sur les deux murs quand la balle venait de la gauche ou de la droite avec destruction de la brique correspondante.

Aucun niveau n'était géré.

Les rebonds hauts et bas sur les briques n'ont pas été gérés.

Il manquait probablement une trentaine d'heures pour aboutir à la correction donnée dans ce chapitre, qui est celle de l'enseignant tuteur.

Du point de vue programmation, ce casse briques est devenu un peu complexe pour des étudiants de niveau L2.

Projet casse-briques (suite) pour 2012/2013

modifier


Cette section utilise les RAM spécifiques des F.P.G.A. de chez Xilinx. Il est donc pas inutile d’en rappeler la terminologie (déjà évoquée dans le chapitre Interfaces VGA et PS2).

Nous allons améliorer la partie matérielle du projet précédent de trois manières :

  • affichage en mode texte dans la partie basse : score sur 4 digits et level sur deux digits.
  • interfaçage au cœur AVR pour pouvoir mettre à jour ce score et ce level.

Faute de temps, nous ne voulons pas laisser les étudiants réaliser une partie matérielle trop importante : sur les 60 heures de projet, nous ne voulons en aucun cas dépasser les 30 heures de conception matérielle. Nous allons ainsi leur donner la gestion VGA toute faite. Il faudra donc réaliser l'interface avec 4 PORTs de l'AVR pour pouvoir écrire dans la mémoire et pouvoir ainsi changer les valeurs numériques.

Peut-on gagner de la mémoire RAM ?

modifier

Nous avons déjà eu l’occasion de présenter les diverses modifications que nous avons réalisé sur la mémoire programme pour pouvoir utiliser data2mem (voir le chapitre correspondant). Cette année il nous faut réaliser un travail un peu identique sur la mémoire RAM.

La mémoire RAM de l'ATMega8 a une taille de 1 ko (celui qui est vendu par ATMEL). Dans le cœur original de chez OpenCore cette RAM est réalisée à l'aide de quatre RAMB4_S4_S4 (possédant deux PORTs de 4 bits soit 4 ko en tout). En clair nous avons plus de mémoire RAM que prévu : c’est bien. Mais est-ce que le compilateur C est capable de l’utiliser ?

D'un autre côté quel gaspillage ! Le F.P.G.A. spartan 3 utilisé ne possède que des RAMB16 mais chaque bloc de RAMB4 prend en fait un bloc de RAMB16. En clair on gaspille 16 ko pour en faire 4 ko !!! Le problème c’est que la ROM programme plus la RAM ainsi réalisée utilisent toutes les RAMB16 du spartan3 !!! Or il nous faut réaliser une ROM de caractères pour afficher notre texte.

Si l’on prend en compte les programmes déjà réalisés, on peut s'apercevoir qu’ils ne sont pas très consommateurs de RAM : ainsi l’idée consistant à remplacer 4 RAMB4 par une RAMB16 de 2 ko ne pénalisera certainement pas nos programmes. Cela libère ainsi 3 RAMB16 : une sera entièrement utilisée par notre ROM de caractères, une autre sera utilisée pour les briques et le texte à afficher. Il nous en reste une pour faire évoluer cet ensemble.

Cette modification a déjà été réalisée dans la correction du projet précédent (celui de 2011/2012 voir le répertoire /CorrProjet2011 du fichier ATMega8_pong_VGA.zip si vous utilisez le fichier vgaTopCassebrique.vhd en lieu et place de vgaTopCassebrique2.vhd)

Travail à réaliser

modifier

Comme d'habitude ce travail se décompose en une partie matérielle et une partie logicielle.

Partie matérielle

modifier

La connexion de PORTs pour l'ATMega8 a déjà été évoquée dans le projet précédent. On rappelle donc que le projet 2011 a réalisé la connexion du module VGA de la manière suivante :

 
Travail réalisé pour une connexion l'année précédente

Nous allons présenter maintenant ce qu’il va vous falloir réaliser.

 
Travail réalisé pour une connexion cette année

Comparez les deux figures avant de commencer quoi que ce soit. Comme d'habitude les (+) désignent des entrées ou sorties à ajouter et les (-) la même chose mais à retirer. Nous avons retiré tous les + de l'année dernière car tout est déjà prêt cette année.

choix technologiques
E/S module VGA E/S AVR
x_rect<9:8> PORTD<1:0>
x_rect<7:0> DDRD<7:0>
y_rect<9:8> PORTB<1:0>
y_rect<7:0> DDRB<7:0>
y_raqG<7:0> PORTC<7:0>
y_raqD<7:0> DDRC<7:0>
ligne1<7:0> PORTA<7:0>
ligne2<7:0> DDRA<7:0>
AddrL<7:0> TWAR<7:0>
AddrH<7:0> TWDR<7:0>
Data<7:0> ADCL<7:0>
CommandBus<7:0> ADCH<7:0>

Nous avons donc choisi les tout premiers registres non utilisés. Ceci est réalisé dans le fichier io2.vhd avec :

    -- 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 X"22" => AddrL <= I_DIN; --TWAR
                  when X"23"  => AddrH <= I_DIN;  --TWDR
                  when X"24"  => Data <= I_DIN;  --ADCL
                  when X"25"  => CommandBus <= I_DIN;  --ADCH
		 when X"31" => Balle_xLow <= I_DIN; --DDRD
		 when X"32" => Balle_xHigh <= I_DIN; --PORTD
                  when X"37"  => Balle_yLow <= I_DIN;  --DDRB
		 when X"38" => Balle_yHigh <= I_DIN; --PORTB
		 when X"34" => raqD_y <= I_DIN; --DDRC
		 when X"35" => raqG_y <= I_DIN; --PORTC
		 when X"3B" => s_ligne1 <= I_DIN; -- PORTA
		 when X"3A" => s_ligne2 <= I_DIN; -- DDRA
--		 when X"3D" => s_scoreg <= I_DIN; -- EEDR : EEPROM Data Register
                  when X"40"  =>  -- 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;

Voila en quatre points le travail à réaliser en VHDL :

  • Éventuellement, modifier le fichier vgaTopCasseBrique2.vhd pour ramener le contenu de la mémoire RAMB16_S9, qui contient le texte à afficher, en adresse 0.
  • Ajouter quatre PORTs de sortie au cœur ATMega8 dans le fichier io2.vhd. Trois PORTs peuvent suffire car nous n'aurons jamais à écrire au-delà de 256 octets (8 bits d'adresse).
  • Ajouter un PORT à la RAMB16_S9 en la transformant en RAMB16_S9_S9 dans le fichier vgaTopCasseBrique.vhd
  • Connecter les trois ou quatre PORTs de l'ATMega8 au PORT ajouté de la RAMB16_S9_S9.

Voici le fichier io2.vhd :

Voici le fichier VGATopCassebrique.vhd :

Partie logicielle

modifier

En commençant par réaliser une balle qui rebondit correctement sur les raquettes, vous pourrez améliorer votre programme petit à petit pour que votre balle soit capable de rebondir sur des briques en les détruisant. Affiner ensuite vos rebonds sur les briques en particulier avec les rebonds sur les faces horizontales des briques.... Gérer ensuite l’affichage du "level" et pour finir celui du "score" à votre convenance.

Projet Pong et téléphones portables (2012/2013)

modifier

Il s'agit de réaliser un jeu de Pong dans un FPGA avec un processeur ATMega8 comme nous l'avons décrit dans le chapitre Embarquer un Atmel ATMega8. Nous avons décidé aussi d'ajouter un chapitre qui traite, entre autres, des possibles interfaces Android/FPGA : Les nouvelles interfaces : de la Nunchuk de Nitendo à Android.

Travail à réaliser

modifier

On vous demande de vous initier à la programmation Android. Il n’est pas difficile de la réaliser directement sur une tablette même bien plus facile que sur un PC. En effet l'installation du SDK et des machines virtuelles sur un PC est très longue.

Réalisation matérielle et logicielle

modifier

Deux téléphones portables Android (remplaçables par des tablettes Android) vont commander deux raquettes.

  • La partie Android utilisant l'USB est composée d'un PIC 24F qui gère l'USB maître. C'est donc ce PIC 24F qui devra communiquer avec le FPGA. Nous avons choisi de communiquer par la RS232 (mais en 0/3,3 V).
  • La partie Andoid utilisant le bluetooth est composée par un FB155BC de chez Firmetch qui s'interface par défaut en 9600 bauds, pas de parité, 1 bit de stop (sans protocole matériel).

La conséquence immédiate est qu’il nous faut un processeur capable de gérer deux liaisons rs232. Nous n'en avons pas ! L'ATMega8 utilisé jusqu'à maintenant en possède une seule pour laquelle la vitesse est fixée à 38400 bauds. Nous allons utiliser celle-ci pour communiquer avec le PIC24F. Celle qui doit fonctionner avec le bluetooth sera ajoutée de toute pièce.

Liaison série dans le PIC24F

modifier

Le PIC 24F laisse la liberté d'utilisation des broches dédiées. Je m'explique.

Si vous regardez le brochage d'un PIC24F, vous ne verrez aucune broche où apparaît "RXD" ou "TXD" : vous pouvez les mettre n’importe où. C'est pratique mais c’est une chose supplémentaire qu’il faut apprendre à gérer. Nous avons eu bien du mal à trouver un exemple. Le seul livre sur le PIC24F dont nous disposions parle de la RS232 sans évoquer ce problème ! Mais comme la platine Android de chez Microchip n'utilise pas la broche rs232 standard (du kit de développement), il faut donc réaliser sa déclaration. Ceci se fait avec le code c suivant :

#include <PPS.h> /*Pin Re-Mapping peripheral library functions */
//...................
void initU2(void) {
    _TRISD0=0;       // set RD0 to output; this is pin 6 which is U1TX
     /* Initialize peripherals */
 PPSUnLock;
 //iPPSInput(IN_FN_PPS_U1RX,IN_PIN_PPS_RP10); // Assign U1RX To Pin RP10
 //iPPSInput(IN_FN_PPS_U1CTS,IN_PIN_PPS_RP1); // Assign U1CTS To Pin RP1
 iPPSOutput(OUT_PIN_PPS_RP11,OUT_FN_PPS_U1TX); // Assign U1TX To Pin RP11
 //iPPSOutput(OUT_PIN_PPS_RP3,OUT_FN_PPS_U1RTS); // Assign U1RTS To Pin RP3
 PPSLock;
 ///CloseUART1();
    U1BRG = BRATE;
//.........................

Le kit Android possède bien une E/S dédiée mais pas le composant. Si vous avez bien compris, vous voyez qu'on utilisera le port série numéro 1 (il y en a un deuxième) et que la transmission se fera sur la broche RP11.

Programme d'exemple pour la première UART

modifier

La première UART est celle que l’on a en bonus avec le cœur ATMega8.

Nous donnons pour information un programme qui gère la communication rs232 de notre ATMega8. La réception peut être gérée par interruption. Rappelons qu'ici la transmission est fixée en dur comme :

  • 38400 bauds
  • pas de parité
  • 8 bits de données
  • 2 bits de stop.
#include <avr/io.h>
#undef F_CPU
#define F_CPU 25000000UL
#include <avr/interrupt.h>
#include "util/delay.h"

// interruption de réception
ISR(USART_RXC_vect) // ISR(_VECTOR(11))
{
        PORTB = UDR;
}

//**********************************************************************************************************
// function uart_init()
// purpose: put character in first rs232 PORT
// arguments:
// corresponding character
// return:
// note: 38400,8,n,2 hard coded : transmission only
//**********************************************************************************************************
void usart_init(void) {
  UCSRB = (1<<TXEN); // transmission
// UCSRC = (1<<UCSZ1)|(1<<UCSZ0)|(1<<URSEL); // ignoré par notre matériel
// UBBRL = 0x33; //pas pour nous car 38400 bauds Sans 8,2
}

//**********************************************************************************************************
// function uart_send()
// purpose: put character in first rs232 PORT
// arguments:
// corresponding character
// return:
// note: 38400,8,n,2 hard coded
//**********************************************************************************************************
void usart_send(unsigned char ch){
  while(!(UCSRA & (1<<UDRE)));
  UDR = ch;
}

int main(void)
{
    UCSRB = (1<<RXEN)|(1<<RXCIE)|(1<<TXEN); // pour pouvoir déclencher interruption RS232
    sei(); // autorise interruption générale
// DDRB = 0xFF; //make PORTB an output
// usart_init(); // écraserait la première ligne du main qui fait le travail
    for (;;) {
      usart_send('A');
      _delay_ms(1000);
    }
  return 0;
}

La réception sans interruption peut se faire avec quelque chose comme :

while (!(UCSRA & (1<<RXC))); //attente donnée RS232
PORTB = UDR;

Intéressons-nous maintenant à la deuxième UART.

Construction matérielle de la deuxième UART

modifier

Les lecteurs qui ont suivi les projets un par un jusqu'ici sont habitués aux modifications du fichier "io.vhd" d'ailleurs transformé pour l’occasion en "io2.vhd". Mais nous nous sommes contentés jusqu'à présent d'ajouter de simples PORTs, ce qui se faisait dans un process appelé "io_wr". Ce que nous allons faire ici est un peu différent : cela se passe dans le process "io_rd" car nous avons des registres en entrée (deux pour être précis ADCH et ADCL). La gestion de la sortie est différente et se fera grâce à une équation combinatoire.

Voici comment est faite la partie matérielle d'envoi dans le fichier io2.vhd (tous les détails ne sont pas donnés) :

baud: mod_m_counter 
    generic map (
      N => 8, -- number of bits
      M => 163 -- mod-M = 25 000 000 / 16x9600
    )
    port map (
      clk => I_CLK, 
      reset => I_CLR,
      max_tick => s_16x,
      q =>open
    );
rs232_tr: Xilinx_uart_tx Port map(
      data_in => I_DIN, --rs232_data,
      write_buffer => L_WE_UART2, --change this value !!!!
      reset_buffer => I_CLR,
      en_16_x_baud => s_16x,
      serial_out => serial_out,
      buffer_full => s_buffer_full2,
      buffer_half_full => s_half_full2,
      clk => I_CLK);
		
rs232_rc:Xilinx_uart_rx Port map(
      serial_in => serial_in,
      data_out => U_RX_DATA2,
      read_buffer => L_RD_UART2,
      reset_buffer => I_CLR,
      en_16_x_baud => s_16x,
      buffer_data_present => s_data_present,
      buffer_full => s_buffer_full,
      buffer_half_full => s_half_full,
      clk => I_CLK);

-- 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"24" => Q_DOUT <= U_RX_DATA2; -- ADCL pour réception
		when X"25" => Q_DOUT <= -- ADCH:
                               s_data_present    -- Rx data present MSB.
                             & s_buffer_full     -- Rx buffer full.
                             & s_half_full       -- Rx buffer half full.
                             & s_buffer_full2    -- Tx buffer full.
                             & s_half_full2      -- Tx buffer half full.
                             & '0'               -- unused
                             & '0'               -- unused
                             & '0';              -- unused
-- la suite n’est pas donnée
-- bla - bla - bla
    end process;
-- ici l'adresse du PORT est décidée :
L_WE_UART2 <= I_WE_IO when (I_ADR_IO = X"24") else '0'; -- write UART2 ADCL
L_RD_UART2 <= I_RD_IO when (I_ADR_IO = X"24") else '0'; -- read UART2 ADCL

L'avant dernière ligne du programme VHDL ci-dessus décide qu'une écriture dans la RS232 se fera par le registre ADCL d'adresse 0x24.

Relisez Interfaces RS232 et USB pour comprendre l’utilisation des logicores Xilinx. Notez cependant que nous avons choisi les anciens logicores (8 bits au lieu de 9) pour ne pas gérer de parité. Ceci parce que ceux-ci seront connectés au module Firmtech qui ne gère pas de parité.

Programme d'exemple pour la deuxième UART

modifier

La deuxième UART a été réalisée avec les logicore Xilinx (avec un FIFO d'ailleurs). Pour information nous avons été obligé de renommer les entités en les préfixant par "Xilinx_" car ces entités existaient déjà dans le cœur ! Pareil pour les noms de fichier ! Sans quoi c’est la grande pagaille dans le projet !

Nous avons aussi décidé d’utiliser le registre ADCL pour cette fonction. C'est un registre, qui pour un ATMega8 de la vraie vie, est associé à la conversion analogique numérique. Nous n'en n'avons pas dans notre FPGA, il est donc libre pour en faire ce que l’on veut.

Voici un exemple en C qui envoie un caractère.

// Constantes
#define TXBF 4 //TXBF : buffer full en transmission
#define RXDP 7 //RXDP : data present en réception

//**********************************************************************************************************
// function uart_send2()
// purpose: put character in second rs232 PORT
// arguments:
// corresponding character
// return:
// note: 9600,8,n,1 hard coded
//**********************************************************************************************************
void usart_send2(unsigned char ch){
  while (ADCH & (1<<TXBF)); //attente si Buffer Full en transmission
  ADCL = ch;
}

//**********************************************************************************************************
// function uart_receive2()
// purpose: read character in second rs232 PORT
// arguments:
// corresponding character
// return: non-blocking sub return 1 if no data present else return char
// note: 9600,8,n,1 hard coded, non-blocking sub return 1 if no data present 
//**********************************************************************************************************
char usart_receive2(void){
  if (ADCH & (1<<RXDP)) //attente tant que Data Present en réception
    return ADCL;
  else return 1;
}

//**********************************************************************************************************
// function bluetoothWaitOK()
// purpose: wait "OK" string second rs232 PORT
// arguments:
// corresponding character
// return: return 2 if "OK" recieved 
// note: 9600,8,n,1 hard coded, blocking if "OK" never comes
//**********************************************************************************************************
unsigned char bluetoothWaitOK(void){
  unsigned char ok=0;
  // attente du 'O'
  do {
    while (!(ADCH & (1<<RXDP)));//attente tant que Data Present en réception
    if (ADCL == 'O') ok++;
  } while(ok !=1);
  // lecture d'un seul caractère 
  while (!(ADCH & (1<<RXDP)));//attente tant que Data Present en réception
  if (ADCL == 'K') ok++;
  return ok;
}

Un programme principal du genre :

int main(void)
{ 
    char ch;
    for (;;) {
      usart_send2('A');
      _delay_ms(1000);
      ch = usart_recieve2();
      if (ch != 1) usart_send2(ch);
    }
  return 0;
}

peut être utilisé. Ce programme insère les caractères reçus parmi les 'A' affichés dans l'hyperterminal. Les caractères envoyés ne sont pas perdus grâce au buffer de 16 caractères qui a été gardé.

Annexe : fichier ucf utile pour la carte Spartan3E

modifier

Voici le fichier ucf pour la carte spartan 3E. Ce fichier n’intéressant pas les utilisateurs d'autres cartes et d'autres fondeurs, nous le mettons dans une boîte déroulante.

Éléments de solution Android

modifier

Un programme pour tablette Android a été développé. Dans un premier temps il devait présenter un écran avec deux boutons "monter" "descendre" destinés à monter et descendre la raquette par l'intermédiaire du bluettoth. Puis une version utilisant le gyroscope de la tablette a été mise en œuvre. Ces programmes ont été développés par les étudiants Benjamin EUVRARD et Thomas GRIMONT.

Voici donc sans commentaire particulier :

  • le fichier manifess.xml
<manifest package="com.pong" android:versionCode="1" android:versionName="1.0"><uses-sdk android:minSdkVersion="8" android:targetSdkVersion="11"/><uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/><uses-permission android:name="android.permission.BLUETOOTH"/><application android:icon="@drawable/ic_launcher" android:label="@string/app_name"><activity android:label="@string/app_name" android:name=".MainActivity" android:screenOrientation="portrait"><intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter></activity><activity android:name=".Gyroscope"/><activity android:name=".Boutons"/></application></manifest>
  • le fichier MainActivity.java
  • Le fichier Boutons.java
  • fichier Gyroscope.java

Projet pacman pour 2012/2013

modifier

La partie gestion d'écran pour pacman est maintenant développée, et nous allons l'interfacer à l'ATMega8. Pour le moment vous pouvez lire aussi :

  • pacman dans wikipédia qui vous rappelle les règles et la terminologie associée.
  • qu'appelle-t-on un sprite dans un jeu vidéo ?
  • le site pacman chez opencores semble intéressant. Il s'agit d'un pacman complet, sans processeur et en verilog.
  • Construction de la gestion d'écran correspondant avec un décor et des sprites dans ce livre (A LIRE ABSOLUMENT).

Commençons par quelques explications.

Comment gérer les positions des sprites

modifier

L'écran vidéo doit être commandé par un certain nombre de PORTs et une mémoire vidéo. La mémoire vidéo étant abordée un peu plus loin nous allons nous concentrer sur ici sur la façon d’utiliser les PORTs. Il nous faut commander les choses suivantes :

  • coordonnées x et y du pacman
  • coordonnées x et y de l'ennemi
  • le numéro de sprite

C'est donc au processeur de gérer cet ensemble de données. Le numéro de sprite est présent pour pouvoir changer de type de pacman suivant qu’il monte, descend va à gauche et à droite.

Comme d'habitude c’est le fichier "io2.vhd" qui est responsable de la gestion des PORTs. En voici un extrait assez significatif :

    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
		-- internal VGA interface
		 when X"32" => Q_LEDS <= I_DIN; --PORTD
		 when X"38" => s_sprites_numbers <= I_DIN; --PORTB
		 when X"34" => s_enemy_y <= I_DIN; --DDRC
		 when X"35" => s_pacman_y <= I_DIN; --PORTC
		 when X"3B" => s_pacman_x <= I_DIN; -- PORTA
		 when X"3A" => s_enemy_x <= I_DIN; -- DDRA
                   when X"40"  =>  -- 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;

Vous avez assez de données dans cet extrait VHDL pour écrire des sous-programmes demandés un peu plus loin. Si vous avez été attentif vous aurez remarqué que seuls huit bits sont utilisés pour les coordonnées alors qu'en principe 9 sont nécessaires. On a effectivement décidé de laissé tomber le bit de poids faible.

Comment interfacer notre décor

modifier

Rappelons à ce point que le décor, ce qui apparait à l'écran est dans une RAM. Il faut à tout prix que le processeur ait accès à cette mémoire en écriture et lecture pour changer le décor (quand il mange une pac_gomme par exemple).

Dans le projet précédent de cette même année, nous avons choisi d'interfacer la mémoire écran à travers des PORTs. C'est très facile à réaliser mais assez inefficace : rien que pour réaliser l'adresse il faut écrire dans deux PORTs. Pour mettre la donnée il faut écrire dans un autre PORT et ensuite écrire quelquefois dans le PORT de commande pour réaliser les bons signaux. Regardez pour vous convaincre comment se fait le changement du niveau (level en anglais) par écriture dans la RAM dans l'autre projet (Casse-briques):

//******************************************************************************************************************************
// function putLevel()
// purpose: put level in the screen by writing in ROM
// arguments:
// corresponding level
// return:
// note:
//******************************************************************************************************************************
void putLevel(uint8_t level){
  uint8_t Data;
  Data = level & 0x0F;
  ADCH = 0;
  TWAR = 102; // adresse
  ADCL = Data + '0'; //donnée
  ADCH = 3; // commande
  ADCH = 0;
  Data = level & 0xF0;
  Data >>= 4;
  TWAR = 101; //adresse
  ADCL = Data + '0'; //donnée
  ADCH = 3; // commande
  ADCH = 0;
}

Et encore on n'utilise ici qu'une adresse sur 8 bits, ce qui est impossible pour le pacman !

Il nous faut donc trouver une autre technique.

Projeter la RAM vidéo dans la mémoire RAM du processeur

modifier

Tout d’abord quelques remarques d’usage : ce que nous allons faire dans cette section n'est absolument pas réalisable avec un processeur du commerce... enfin, en tout cas, nous ne voyons pas très bien comment faire ! Cela rend naturellement les explications qui suivent pas très faciles à suivre !

Limitations RAM de l'ATMega8 du commerce

modifier

Un ATMega8 du commerce possède 1 ko de RAM et cette RAM n'est absolument pas visible de l'extérieur. Si vous voulez la transférer à l'extérieur il vous faudra câbler une SRAM externe et utiliser quelques PORTs de l'ATMega8 pour y écrire des choses. Et si vous voulez qu'un circuit externe vienne lire cette mémoire, c’est là que les problèmes commencent : n'est-ce pas pour ce genre de situation que l’on a inventé l’accès direct à la mémoire (DMA) ? La bonne nouvelle c’est que nous nous passerons de cela car les mémoires des FPGA modernes peuvent avoir plusieurs ports (ce sera deux ports pour nous car nous utilisons un FPGA assez ancien (2003)).

Une autre question importante est de savoir si votre compilateur C connait les limitations RAM de votre ATMega8. Autrement dit, si vous essayez d'écrire au-delà de votre kilo octet votre compilateur vous enverra-il un message d'erreur? Quelques essais nous ont convaincu que ce n'était pas le cas pour le compilateur du GNU. C'est un bon signe pour nous.

Et notre cœur ATMega8

modifier

Nous avions remarqué depuis le début que la version originale du cœur contenait 4 RAMB4_S4_S4 comme RAM, ce qui fait 2 ko. C'est le double du microcontrôleur original. Nous avons même vu une version avec 4 RAMB16_S4_S4 ce qui fait 8 ko de RAM... et nous venons de voir que le compilateur savait gérer tout cela (sans aucune option de compilation d'ailleurs).

Muni de cette expérience, nous allons prendre la partie VGA développée dans un autre chapitre. Elle contient une RAMB16_S9 pour gérer le décor (le fond d'écran) qui a été étendue en RAMB16_S9_S9, c'est-à-dire qu'on lui a ajouté une interface de lecture/écriture. Et nous allons nous arranger pour que cette deuxième interface soit reliée au cœur ATMega8. Une réussite nous permet d'envisager de changer ce que l’on veut du décor en écrivant ou lisant directement dans la RAM processeur. Fini les PORTs pour modifier le contenu de la RAM vidéo, de simples variables bien gérées suffisent. Pas convaincu ? Voici comment on change le Level dans le pacman sans aucune manipulation de PORTs :

#define LEVELUNIT 1958+1024+34+900 //debut memoire(2048)+2*derniere ligne(900)+2*offset dans ligne(34)
//....
//**********************************************************************************************************
// function putLevel()
// purpose: put level in the screen by writing in RAM (RAMB16_S9_S9)
// arguments:
// corresponding level
// return:
// note:
//**********************************************************************************************************
void putLevel(uint8_t level){
  unsigned char *affscore;
  affscore = (unsigned char *)LEVELUNIT; //debut memoire+derniere ligne+offset dans ligne 
  *affscore = (level &0x0F)+'0';
  affscore--;affscore--;
  *affscore = ((level &0xF0)>>4)+'0'; 
}

Nous devons reconnaître qu’il faut manipuler le language C avec brio pour comprendre cela. Mais l’idée générale est la suivante :

  • déclarer un pointeur
    unsigned char *affscore;
  • donner une valeur au pointeur
    affscore = (unsigned char *)LEVELUNIT;
    où LEVELUNIT est une constante qui définit l'adresse qui nous intéresse. C'est ici qu'est la subtilité.
  • changer une valeur à cette adresse consiste maintenant à écrire
    *affscore=quelque-chose

Nous pensions dans un premier temps que tout bon compilateur n'aurait pas dû nous permettre d'écrire n’importe où dans la RAM... mais finalement trouvons cela bien pratique.

Peut être un autre détail, pourquoi décrémente-t-on l'adresse deux fois entre l'écriture des unités et celle des dizaines? Voici un schéma qui permet l'explication :

 
Comparaison de la RAM de la vraie vie avec celle de notre cœur

À gauche de cette figure on a représenté la RAM des micro-controleurs ATMega8. Pour une raison qui ne nous parait pas évidente, Juergen Sauermann, le concepteur du cœur ATMega8, a organisé la mémoire RAM par mot de 16 bits. Cela nous permet de l'organiser comme en partie droite : deux RAMB16_S9 côte à côte. Celle de droite correspond aux adresses paires tandis que celle de gauche correspond aux adresses impaires. Si vous voulez rester dans la partie notée décor (à droite donc, et pour les adresses hautes), il vous faut donc incrémenter les adresses de deux en deux pour les laisser paires !

Travail à réaliser

modifier

En fait notre ATMega8 n’est pas conçu pour le genre de travail que nous cherchons à réaliser. Si vous regardez tout ce qui a été fait avec ce processeur jusqu'à présent, vous vous rendrez vite compte que l'ajout du matériel s'est toujours fait avec le fichier io.vhd où sont décrits les registres internes. Mais nous voila avec un périphérique très particulier qui contient une zone mémoire. Or la RAM interne est gérée dans un autre fichier... il faut donc faire communiquer ces deux fichiers qui sont bien sûr enfouis dans d'autres. Cela nécessite une bonne connaissance de ce cœur qu'aucun étudiant de L2 ne peut avoir. Nous avons donc décidé de vous fournir entièrement la partie matérielle toute faite. Votre travail consistera donc essentiellement à programmer, et croyez-moi, il y a beaucoup de travail. Pour que vous ayez quand même un petit peu de matériel à réaliser, on vous demande simplement d'interfacer un mini-mini joystick fourni.


Le répertoire /pacman2012 du fichier ATMega8_pong_VGA.zip contient l’ensemble matériel de ce projet (avec la manette Nunchuk). Vous n'aurez qu’à adapter le fichier ucf correspondant pour disposer d'un pacman complet. Un coup d’œil cependant vous montrera qu’il manque l'essentiel : le programme C que nous allons présenter maintenant.

Développement logiciel

modifier

On vous demande de développer un certain nombre de sous-programme et de les tester au fur et à mesure. Commencez par montrer que le sous-programme "putLevel" donné plus haut fonctionne.

  • Développer un sous-programme putScore sur le même principe.
  • À partir de maintenant, amusez-vous à interfacer le joystick et votre pacman en laissant tomber enemy à l'aide de
//******************************************************************************************************************************
// function setpacmanXY()
// purpose: put the pacman with x and y coordinates
// arguments:
// corresponding x and y coordinates
// return:
// note:
//******************************************************************************************************************************
void setpacmanXY(uint8_t x,unsigned char y){ 
// voir fichier io2.vhd pour comprendre
  PORTA=x; 
  PORTC=y;
}

Vous ne pourrez pas aller plus loin sans savoir si vous heurtez le mur ou pas.

  • Compléter le sous-programme
//******************************************************************************************************************************
// function computePossibleWays()
// purpose: compute the possible way starting from x and y coordinates
// arguments:
// corresponding x and y coordinates
// return: bits as Up in b3, Down in b2, Left in b1 and right in b0
// note: 
//******************************************************************************************************************************
unsigned char computePossibleWays(unsigned char x,unsigned char y){
  unsigned char *ramB16,res; 
  unsigned int addr;
  res=0;
  addr = computeTileAddress(x,y);
  ramB16 = (unsigned char *)(addr - 128); // 64 en principe mais mémoire organisée ainsi : up
  if (*ramB16 != 0x07) res=res|0x08; //si pas brique on peut y aller

  //****** COMPLETER ICI  avec exemple ci-dessus *******

  return res;
};

dont l'objectif est de savoir pour toutes les positions vers quelles directions il est possible d'aller sans heurter un mur. C'est le cœur de votre travail, mais ce sous-programme est assez difficile à développer. Ce qui le rend si difficile est qu’il faut y refaire ce qui a été fait en VHDL :

  1. trouver le numéro de pavé à partir de x et y avec la concaténation VHDL. La difficulté et qu'ici x et y sont sur 8 bits, c'est-à-dire qu'on a perdu les deux bits de poids faibles (de pixel_x et pixel_y en VHDL). Soyez donc méthodique.
  2. en déduire l'adresse mémoire du pavé dans lequel nous sommes. La formule est simple mais c’est pas pour cela que vous allez la trouver du premier coup.
  3. en déduire si le pavé supérieur, inférieur, droite et gauche contiennent ou non des murs (code 0xO7)

Pour vous aider nous vous donnons un sous-programme qui fait les deux premiers points :

//******************************************************************************************************************************
// function computeTileAddress()
// purpose: compute the address of the tile where I am with x and y coordinates
// arguments:
// corresponding x and y coordinates
// return: address
// note: you cannot understand if you never read the corresponding VHDL code.
//******************************************************************************************************************************
  unsigned int computeTileAddress(unsigned char x,unsigned char y){
  unsigned char xc,yc;
  unsigned int val,addr;
// obtention du nemero de pavage de l'écran VGA
  xc = x + 1; //x + 8 en principe mais on a perdu volontairement les 2 poids faibles dans x
  yc = y + 2; //Y + 16 en principe mais on a perdu volontairement les 2 poids faibles dans y
  val = xc >> 2; // je garde les six bits de poids fort
  addr = yc & 0x0078; // je garde les quatre bits de poids fort
  addr <<= 3;      // je laisse la place pour concaténation
  addr = addr + (val & 0x3F); // J’ai mon numéro de pavage ici
  // quelle est son adresse en RAMB16_S9_S9 ?
  addr = (addr+1024)<<1; //debut de l'adresse du début de pavage
  return addr;
}

Ce sous-programme calcule d’abord les coordonnées du centre du pacman (ou de l'ennemi) puis calcule l'adresse courante en mémoire vidéo sur laquelle on se trouve. Muni de cette adresse, il n’est pas très difficile de trouver ce qui l'entoure. Pour information nous avons testé le sous-programme complet "computePossibleWays" en manipulant l’affichage du score comme ceci :

// utilisation du score pour vérifier la detection de Up Down Left Right
           score=0;
           if (theWay & 0x08) score|=0x1000;
           if (theWay & 0x04) score|=0x0100;
           if (theWay & 0x02) score|=0x0010;
           if (theWay & 0x01) score|=0x0001; 
           putScore(score);

qui utilise putScore qui est la première chose que l’on vous demande de faire.

  • Le reste est laissé à votre initiative. Mais gardez à l'esprit que votre premier objectif est de laisser tomber l'ennemi pour vous concentrer sur le pacman. Il ne faut pas que ce pacman puisse traverser une brique ! Après seulement, faites le manger les pac-gommes.

Développement matériel

modifier

On vous demande de développer une interface matérielle I2C capable de gérer la manette Nunchuk. Cela doit se réaliser en deux étapes :

  • gestion de la communication avec la manette Nunchuk à partir du projet de Eli Smertenko i2c Master Slave chez Opencores.org. On utilisera éventuellement un processeur pour faire les tests,
  • adaptation de la première étape à notre processeur ATMega8. Le micro-contrôleur ATMega8 gérant lui aussi l'I2C avec l'interface TWI (Two Wire Interface) nous nous attacherons à respecter du mieux que nous pourrons la documentation officielle de celui-ci.

La documentation de l'ATMega8 s’appelle "doc2486.pdf" : téléchargez-la et allez en page 169, c’est là que le travail sérieux commence...


Correction partielle

modifier

Nous allons donner la correction partielle de ce projet.

La partie matérielle

modifier

Rappelons que le répertoire /pacman2012 du fichier ATMega8_pong_VGA.zip contient l’ensemble matériel de ce projet (avec la manette Nunchuk). Vous n'aurez qu’à adapter le fichier ucf correspondant pour disposer d'un pacman complet.

Signalons que nous avons l'intention d'arrêter de documenter ce chapitre avec les projets des années futures :

  • le fichier ATMega8_pong_VGA.zip est devenu ingérable : il contient diverses version du cœur ATMega8, ce qui fait qu'un débutant aura bien du mal à s'y retrouver.
  • nous allons commencer un nouveau chapitre du cours pour gérer le futur de ce cœur en essayant de le transformer en ATMega16 ou même ATMega32.
  • le pacman correspondant aura deux (ou plus) ennemis ce qui n'était pas le cas cette année.

Nous avons assez de recul maintenant pour partir d'un cœur fiable qui contient l'essentiel de ce que l’on veut et de le faire évoluer sans changer de version de cœur, en tout cas en théorie.

Voici en schématique ce qui a été réalisé :

 
Comment interfacer le module I2C pour lire et transformer les données de la Nunchuk

Un coup d’œil cependant vous montrera qu’il manque l'essentiel : le programme C que nous allons présenter maintenant.

La partie logicielle

modifier

Pour une fois nous devons avouer que nos étudiants sont allés bien plus loin que nous dans la programmation du pacman. Nous allons donner donc les deux solutions.

La solution de l'enseignant tuteur
modifier

Cette version fonctionne correctement mais partiellement seulement :

  • le pacman peut manger le bonus mais ne peut pas poursuivre l'ennemi pour le manger
  • l'ennemi va systématiquement deux fois plus vite que le pacman ! Il y a des moyens de s'en sortir quand même mais cela demande un peu d'expérience.
  • la faiblesse de la poursuite du pacman par l'ennemi se trouve dans la fonction "computeDistance"

Ce programme fait environ 400 lignes.

Les étudiants nous ont fait un programme de près de 700 lignes que nous allons présenter maintenant.

La solution des étudiants
modifier

Quelques remarques sur le code :

  • La gestion de la poursuite du pacman par l'ennemi est gérée d'une manière originale et a été conçue sans l'aide de l'enseignant tuteur. C'est vraiment le point fort de cette solution.
  • Un certain nombre d'éléments sur l'écran qui leur était proposé (par le matériel en VHDL) ont été changé pour s'adapter à leur contexte (par le logiciel).
  • L'apparition de messages "GAME OVER", "YOU WIN", ... a été gérée.

Autre ressource

modifier

Notez qu’à partir de l'année scolaire 2013/2014, ce projet sera porté sur une version ATMega16 de ce processeur (voir Projet pacman avec l'ATMega16). La ressource correspondante se trouve ici : pacman2013.zip

Annexe section pacman : Comment connecter le Joystick provisoire sur Digilent Starter Board Spartan3

modifier

Le pacman sera dans un premier temps commandé par un Joystick provisoire (qui représente en fait 4 interrupteurs gauche, droite haut et bas). Celui-ci est branché sur le connecteur A2 de la spartan 3. Il faudra changer ceci si vous utilisez une autre carte. Nous changerons cette section entièrement quand nous aurons réussi à connecter la manette Nunchuk. Pour le moment, le fichier ucf correspondant est :

Nous utilisons les broches 1, 3, 5, 7, 9 et 11 du connecteur A2, avec la correspondance :

  • 1 est GND
  • 3 est Vcc
  • 5 est Bas géré par I_SWITCH(6) en broche "D5" du spartan3
  • 7 est Haut géré par I_SWITCH(7) en broche "D6" du spartan3
  • 9 est Gauche géré par I_SWITCH(0) en broche "E7" du spartan3
  • 11 est Droit géré par I_SWITCH(1) en broche "D7" du spartan3

Voir aussi

modifier