Very High Speed Integrated Circuit Hardware Description Language/Travail pratique/Autres projets pour ATMEL ATMega8
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.
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
modifierL'écriture du chapitre Programmer in Situ et déboguer nous a obligé à faire évoluer le cœur embarqué coreATMega8.
La gestion d'interruptions et l'implantation de l'instruction SPM ne sont pas nécessaires pour le projet « casse briques » de cette année, mais le simple fait de vouloir utiliser data2mem (décrit lui aussi dans Programmer in Situ et déboguer) nous oblige à utiliser la dernière version de notre processeur ! Cette version n’est pas disponible chez Opencores pour le moment mais nous la mettons à disposition dans le répertoire /AVRComplet16_S4_S4/25MHzAVR/ de notre ATMega8_pong_VGA.zip. C'est la version la plus à jour, version dans laquelle nous avons vérifié les interruptions RS232 et l'instruction SPM.
Cahier des charges
modifierUne 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.
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
modifierTrois 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é
modifierRé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
modifierVoici 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
modifierLe 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 :
Les signes + devant des entrées ou sorties veulent dire que l’on a ajouté ces entrées ou sorties dans les entités correspondantes. On a dessiné seulement trois composants dans cette figure, mais il en existe beaucoup d'autres.
- 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
modifierExercice3 question 3 du chapitre Interfaces VGA et PS2 contient une correction de la partie gestion de l'écran VGA.
Le répertoire /CorrProjet2011 du fichier ATMega8_pong_VGA.zip contient une correction complète du projet 2011/2012.
Partie logicielle
modifierProgrammer 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.
Attachez-vous à comprendre comment le C gère les briques : comment vous savez où sont les briques ? C'est fait ici grâce aux deux tableaux tabmur1 et tabmur2 qui contiennent toutes les informations de position que vous avez dans votre partie matérielle.
// gestion d'un casse brique dans un FPGA = VGA+ATMega8
// Ce programme tient dans environ {{Unité|2|{{Abréviation|ko|kilooctet}}}} sur {{Unité|8|{{Abréviation|ko|kilooctet}}}} dispo
// Il manque cependant de jouabilité !!!!
#include <avr/io.h>
#include "util/delay.h"
#undef F_CPU
#define F_CPU 25000000UL
/* Port A */
//#define PINA _SFR_IO8(0x19)
// Les ports ci-dessous n'existent pas dans l'ATMega8 de la vraie vie mais nous les avons ajouté dans notre cœur
// d'où leur présence ici : ne pas modifier les fichiers d'entête !
#define DDRA _SFR_IO8(0x1A)
#define PORTA _SFR_IO8(0x1B)
//******************************************************************************************************************************
// constants definitions (see VHDL files)
//******************************************************************************************************************************
// Constantes liées au matériel (voir dans VHDL)
// Mes étudiants, par exemple, ont mis les deux murs plus à gauche ce qui change ces constantes
//************** wall 1
#define GX1 420
#define GY 0
#define GDX 20
#define GDY 422
//************** wall 2
#define GX2 440
//******************************************************************************************************************************
// data type definitions and the correspionding arrays
//******************************************************************************************************************************
//L'utilisation pratique des tableaux ci-dessous nous a montré qu’il est possible de retirer le champ xdeb pour économiser de la place mémoire
struct s_brique {
uint16_t xdeb,ydeb,xfin,yfin;
} tabmur1[8]={{GX1-10,GY,GX1+GDX,(GDY-GY)/8},{GX1-10,(GDY-GY)/8 -12,GX1+GDX,2*(GDY-GY)/8},{GX1-10,2*(GDY-GY)/8 -12,GX1+GDX,3*(GDY-GY)/8},
{GX1-10,3*(GDY-GY)/8 -12,GX1+GDX,4*(GDY-GY)/8},{GX1-10,4*(GDY-GY)/8 -12,GX1+GDX,5*(GDY-GY)/8},{GX1-10,5*(GDY-GY)/8 -12,GX1+GDX,6*(GDY-GY)/8},
{GX1-10,6*(GDY-GY)/8 -12,GX1+GDX,7*(GDY-GY)/8},{GX1-10,7*(GDY-GY)/8 -12,GX1+GDX,(GDY-GY)}},
tabmur2[8]={{GX2-10,GY,GX2+GDX,(GDY-GY)/8},{GX2-10,(GDY-GY)/8 -12,GX2+GDX,2*(GDY-GY)/8},{GX2-10,2*(GDY-GY)/8 -12,GX2+GDX,3*(GDY-GY)/8},
{GX2-10,3*(GDY-GY)/8 -12,GX2+GDX,4*(GDY-GY)/8},{GX2-10,4*(GDY-GY)/8 -12,GX2+GDX,5*(GDY-GY)/8},{GX2-10,5*(GDY-GY)/8 -12,GX2+GDX,6*(GDY-GY)/8},
{GX2-10,6*(GDY-GY)/8 -12,GX2+GDX,7*(GDY-GY)/8},{GX2-10,7*(GDY-GY)/8 -12,GX2+GDX,(GDY-GY)}};
//******************************************************************************************************************************
// functions prototypes
//******************************************************************************************************************************
void setXY(uint16_t x,unsigned int y);
void My_delay(unsigned int delay);
void bounceOffTheWall(unsigned int posX,unsigned int posY,unsigned char level,unsigned char levelMin,
struct s_brique tableauData[],signed char *deltaX,signed char *deltaY,unsigned char *mur);
unsigned char PseudoAleat(uint16_t Lim);
//******************************************************************************************************************************
// main
//******************************************************************************************************************************
int main (void)
{
unsigned int posRaqu_16;
unsigned int posX,posY,tempo=250; // change to 200 to increase speed
unsigned char raqD_y=0,raqG_y=0,dizG=0,level=1,line1=0,line2=0,random=0;
signed char deltaX=1,deltaY=1,delta_raqG_y=1;
// int err = (dx>dy ? dx : -dy)/2, e2; montre comment calculer err
int dx=10,dy=5,err=5,e2; //Pour Bresenham
line1 = 0xFF; // level 1
PORTA = line1; //update the wall number 1
DDRA = 0x00; // update the wall number 2
while(1) {
EEDR = level; // two digits level
// starting position
posX=320;
posY=200;
setXY(posX,posY);
deltaX=-1;
//attente du départ et recherche graine du gene pseudoaléatoire
while (bit_is_clear(PINB,PINB0))random = PseudoAleat(12);
while (posX>30 && (line1 || line2)) { //equation booléenne pas facile à trouver
posRaqu_16=raqD_y<<1;
if (posX>=574) { // le rebond est automatique
//Ne pas remettre le test ci-dessous semble poser problème de temps en temps
if (deltaX>0) deltaX= -deltaX; // rebond sur raquette droite
// changement de pente aléatoire
dy = PseudoAleat(12);
}
posRaqu_16=raqG_y<<1; //rebond sur raquette gauche ?
if ((posX<=32) && (posY<posRaqu_16+58) && (posY+10>posRaqu_16) && (deltaX<0)) {
deltaX= -deltaX; // rebond sur raquette gauche
if (delta_raqG_y) { // delta_raqG_y!=0 on change la pente
if ((delta_raqG_y >0 && deltaY >0) || (delta_raqG_y <0 && deltaY <0)) {
dy+=5; if (dy > 15) dy=15; // ne peut dépasser 15
} else {
dy-=5; if (dy < 5) dy=5; // ne peut dépasser 5
}
}
}
// rebond sur mur du bas
if ((posY>=417) && (deltaY>0)) deltaY= -deltaY;
// rebond sur haut de l'écran
if ((posY<=10) && (deltaY<0)) deltaY= -deltaY;
//*********** calcul du rebond sur mur1
bounceOffTheWall(posX,posY,level,3,tabmur1,&deltaX,&deltaY,&line1);
//*********** calcul du rebond sur mur2
bounceOffTheWall(posX,posY,level,6,tabmur2,&deltaX,&deltaY,&line2);
//*********** Début de Brensenham
e2 = err;
if (e2 >-dx) { err -= dy; posX += deltaX; }
if (e2 < dy) { err += dx; posY += deltaY; }
setXY(posX,posY);
//*********** fin de Bresenham
// gestion des raquettes 2bits PORTB/raquette
delta_raqG_y=0;
if (bit_is_set(PINB,PINB6)) if (raqG_y<182) delta_raqG_y=1;
if (bit_is_set(PINB,PINB7)) if (raqG_y>0) delta_raqG_y=-1;
raqG_y = raqG_y + delta_raqG_y;
PORTC=raqG_y;
raqD_y = posY>>1;
if (raqD_y>182) raqD_y=182;
DDRC=raqD_y;
// you want to see the walls :
PORTA = line1; //update the wall number 1
DDRA = line2; //update the wall number 2
My_delay(tempo);
} // end of while (posX>30&& (line1 || line2)) : fin des niveaux
// managing levels in BCD
if (posX>30) {// remove for easier checks
level++;
// remove the comment below if you want speed increases with level
// tempo -=20;
// managing level with 2 digits
if ((level & 0X0F) > 9) {
dizG++;
level = dizG <<4;
} // end if
if (dizG > 9) {dizG=0;level=0;}
} // end if
if (level== 3) line1 = 0xFF; // mur 1
if (level == 2||level== 4) line1 = 0xAA;
if (level >= 5) {
line2 = 0xFF; // ligne2
line1 = 0xFF; // ligne1
}
//*********************************************************
// Rien de particulier n'est fait à partir du niveau 7 !!!!
// On pourrait frapper plusieurs coups pour éteindre une brique
// avec changement de couleurs... Voir futur projet 2012/2013
//*********************************************************
// updating walls
PORTA = line1; //update the wall number 1
DDRA = line2; //update the wall number 2
} //end while(1)
} //end main
//******************************************************************************************************************************
// function setXY()
// purpose: put the ball with x and y coordinates
// arguments:
// corresponding x and y coordinates
// return:
// note:
//******************************************************************************************************************************
void setXY(uint16_t x,unsigned int y){
DDRD=x; //least significant byte
PORTD=x>>8;//most significant byte
DDRB=y; //least significant byte
PORTB=y>>8;//most significant byte
}
//******************************************************************************************************************************
// function PseudoAleat()
// purpose: simple random generator
// arguments:
// corresponding modulo = nb different values :12 for us
// return:
// note:
//******************************************************************************************************************************
unsigned char PseudoAleat(uint16_t Lim){
static uint16_t Y = 1;
Y = (Y * 32719 + 3) % 32749;
return ((Y % Lim) + 4); // offset de 4
}
//******************************************************************************************************************************
// function My_delay()
// purpose: time consuming function to waste time
// arguments:
// corresponding delay
// return:
// note:
//******************************************************************************************************************************
void My_delay(unsigned int delay){
int i;
for(i=0;i<delay;i++) _delay_ms(1);
}
//******************************************************************************************************************************
// function bounceOffTheWall()
// purpose: to bounce off the wall if necessary
// arguments:
// corresponding coordinates of the ball : posX and posY
// level : the current level of the game
// levelMin : from this level on, only a left bounce can destroy a break
// tableauData : data array of the corresponding wall under investigation
// current and calculated coordinates differences : *delatX,*deltaY
// current and calculated bits for the breaks : mur (wall in English)
// return:
// note:
//******************************************************************************************************************************
void bounceOffTheWall(unsigned int posX,unsigned int posY,unsigned char level,unsigned char levelMin,
struct s_brique tableauData[],signed char *deltaX,signed char *deltaY,unsigned char *mur) {
//levelMin = 3 pour premier mur, = 6 pour deuxieme mur
unsigned char i; // compteur de boucle
//tableauData[0].xdeb=GX1-10
if (posX==tableauData[0].xdeb && level > 0 && *mur != 0) // rebond avec effacement
for(i=0;i<8;i++)
if (posY>=tableauData[i].ydeb && posY <= tableauData[i].yfin && (*mur & 1<<i) && *deltaX >0) {
*deltaX = -*deltaX; // rebond
*mur &= ~(1<<i); // on éteint la brique
}
if (posX==tableauData[0].xfin && level > 0) // rebond parfois sans effacement
for(i=0;i<8;i++)
if (posY>=tableauData[i].ydeb && posY <= tableauData[i].yfin && (*mur & 1<<i) && *deltaX <0) {
*deltaX = -*deltaX; // rebond
if (level < levelMin)
*mur &= ~(1<<i); // on efface la brique
}
if (posX > (tableauData[0].xdeb) && posX < (tableauData[0].xfin) && level > 0) { // possibilité de rebond vertical sur brique
if( *deltaY > 0) { // rebond par le haut
for(i=1;i<8;i++) //pas brique du haut
if (posY == tableauData[i].ydeb && (*mur & 1<<i) ) { //tab[i].ydeb contient déjà le -12
*deltaY = -*deltaY; // rebond
if (level < levelMin)
*mur &= ~(1<<i); // on efface la brique
}
} else //autrement change de signe sans arret
// rebond par le bas
for(i=0;i<7;i++) //pas brique du bas
if (posY == tableauData[i].yfin && (*mur & 1<<i) ) {
*deltaY = -*deltaY; // rebond
if (level < levelMin)
*mur &= ~(1<<i); // on efface la brique
}
}
}
- Cette version gère correctement 7 niveaux de jeux : après le septième niveau tout est comme au septième.
- pour le niveau 2 et 4 on est parti du principe qu’il était plus difficile de "descendre" 4 briques que 8 ce qui est faux ! à revoir donc !
- Ce programme tient dans un peu plus de 2 ko : comme nous disposons de 8 ko, nous voyons qu’il est possible de l'améliorer nettement.
- Ce programme manque de jouabilité : quand vous avez une brique à "descendre" vous pouvez certes modifier la pente du rebond mais comme la raquette droite le fait de manière aléatoire, cela rend difficile la visée. Il faudrait peut être remettre en cause le rebond aléatoire sur la raquette de droite !!!
- On ne gère par contre aucun score : ce sera certainement réalisé l'année prochaine avec un affichage en mode caractères.
Conclusion
modifierCe 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
modifierPour des raisons indépendantes de notre volonté, ce projet a du être abandonné surtout parce que nous avons décidé au dernier moment d'interfacer un téléphone portable comme outil de commande du jeu. Le mot "Android" étant alors dans le titre de ce sujet, il a attiré plus d'étudiants que prévu ce qui nous a obligé de doubler les projets Android. Ce changement profond nous oblige à :
- transformer ce sujet (voir la section suivante) puisqu'en fait nous allons réaliser un jeu de Pong sur notre FPGA avec deux commandes par téléphone portable (un par raquette),
- réaliser deux commandes par Android concurrentes et donc écrire un nouveau chapitre dédié à ce problème : Les nouvelles interfaces : de la nunchuk de Nintendo à Android.
La bonne nouvelle, quand même, c’est que nous avions fait une correction qui est donc ajoutée à la fin de cette section.
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).
On appellera dans la suite :
- RAMB4 un RAM/ROM de 4 kbits
- RAMB16 un RAM/ROM de 16 kbits
- RAMB16_S18 une RAMB16 avec 18 bits de données (2 ko) : 16 bits et deux bits de parité non utilisés pour nos besoins.
- RAMB16_S18_S9 une RAMB16 avec deux PORTs indépendants 18 bit de données (16 kbits) pour la partie reliée au processeur et 9 bits pour la partie reliée au FPGA.
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 ?
modifierNous 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
modifierComme d'habitude ce travail se décompose en une partie matérielle et une partie logicielle.
Partie matérielle
modifierLa 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 :
Nous allons présenter maintenant ce qu’il va vous falloir réaliser.
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 :
-------------------------------------------------------------------------------
--
-- 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;
--> added for VGA synchro 18 June 2010
I_CLK_50 : in std_logic;
--< end added
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_SWITCH : 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_7_SEGMENT : out std_logic_vector( 6 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_LEDS : out std_logic_vector( 7 downto 0);
-- added
Q_AN : out std_logic_vector( 3 downto 0);
hsynch,vsynch,red,green,blue : out STD_LOGIC;
--<
Q_TX : out std_logic);
end io;
architecture Behavioral of io is
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;
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;
--<
--> added 2011/11/22
COMPONENT VGAtop IS
PORT (clk_50 : in STD_LOGIC; -- horloge 50MHz
x_rect, y_rect: IN STD_LOGIC_VECTOR(9 DOWNTO 0);
y_raquG, y_raquD: IN STD_LOGIC_VECTOR(7 DOWNTO 0);
-- scoreG : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
ligne1,ligne2 : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
-- interface mémoire
i_Data,i_ADDRL,i_CommandBus : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
hsynch,vsynch,red,green,blue : out STD_LOGIC);
END COMPONENT;
signal Balle_xLow : STD_LOGIC_VECTOR(7 DOWNTO 0);
signal Balle_xHigh : STD_LOGIC_VECTOR(7 DOWNTO 0);
signal Balle_yLow : STD_LOGIC_VECTOR(7 DOWNTO 0);
signal Balle_yHigh : STD_LOGIC_VECTOR(7 DOWNTO 0);
signal raqD_y : STD_LOGIC_VECTOR(7 DOWNTO 0);
signal raqG_y : STD_LOGIC_VECTOR(7 DOWNTO 0);
signal s_ligne1, s_ligne2 : std_logic_vector( 7 downto 0);
signal AddrL, Data, CommandBus : std_logic_vector( 7 downto 0);--AddrH
--<
begin
urt: uart
generic map(CLOCK_FREQ => std_logic_vector(conv_unsigned(25000000, 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/11/22
vga:VGAtop
port map ( clk_50 => I_CLK_50,
x_rect(7 downto 0) => Balle_xLow,
x_rect(9 downto 8) => Balle_xHigh(1 downto 0),
y_rect(7 downto 0) => Balle_yLow,
y_rect(9 downto 8) => Balle_yHigh(1 downto 0),
y_raquG => raqG_y,
-- scoreG => s_scoreg, -- left score managment
y_raquD => raqD_y,
ligne1 => s_ligne1, --left line management
ligne2 => s_ligne2, --left line management
i_Data => Data,
i_ADDRL => ADDRL,
i_CommandBus => CommandBus,
hsynch => hsynch,
vsynch => vsynch,
red => red,
green => green,
blue => blue);
--<
--> 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;
--<
-- 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 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
-- RAM memory interface
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
-- internal VGA interface
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
-- replaced by ROM memory
-- 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;
-- 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;
Voici le fichier VGATopCassebrique.vhd :
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
Library UNISIM;
use UNISIM.vcomponents.all;
-- 24th March 2012
-- Casse-briques avec deux lignes asymétriques
--entité VGA globale
ENTITY VGAtop IS
PORT (clk_50 : in STD_LOGIC; -- horloge 50MHz
-- coordonnées de la balle
x_rect, y_rect: IN STD_LOGIC_VECTOR(9 DOWNTO 0);
-- oordonnées des deux raquettes
y_raquG, y_raquD: IN STD_LOGIC_VECTOR(7 DOWNTO 0);
-- valeurs à afficher sur les scores : 8 bits sur deux digits
-- scoreG : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
-- lignes de briques verticales (deux seulement)
ligne1,ligne2 : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
-- interface mémoire
i_Data,i_ADDRL,i_CommandBus : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
-- en sortie nos cinq signaux VGA
hsynch,vsynch,red,green,blue : out STD_LOGIC);
END VGAtop;
architecture arch of VGAtop is
COMPONENT VGA_SYNC IS
PORT( clock_25Mhz : IN STD_LOGIC;
horiz_sync_out, vert_sync_out : OUT STD_LOGIC;
pixel_row, pixel_column: OUT STD_LOGIC_VECTOR(9 DOWNTO 0));
END COMPONENT;
COMPONENT font_rom is
port(
clk: in std_logic;
addr: in std_logic_vector(13 downto 0);
data: out std_logic
);
end component;
COMPONENT rect IS PORT( --ancien rectangle avant les generic
row,col,x_rec,y_rec,delta_x,delta_y :in STD_LOGIC_VECTOR(9 DOWNTO 0);
colorRGB : in STD_LOGIC_VECTOR(2 DOWNTO 0);
red1,green1,blue1 : out std_logic);
END component;
COMPONENT ligne8briques is
generic (gx, gy, gdx, gdy : natural); --gx, gy = position ; gdx, gdy = tailles
port(e_8 : in std_logic_vector(7 downto 0); -- valeur à affichier
segx, segy : in std_logic_vector(9 downto 0);-- où en est le balayage de l'écran ?
Sseg_red, Sseg_green, Sseg_blue : out std_logic); -- signaux de couleurs
END COMPONENT;
signal pixel_x, pixel_y: std_logic_vector(9 downto 0);
signal pixel_x1_reg,pixel_x2_reg,pixel_y1_reg,pixel_y2_reg: std_logic_vector(9 downto 0);
signal clk25,text_bit_on,video_on,font_bit: std_logic;
signal rgb_reg,rgb : std_logic_vector(2 downto 0);
signal char_addr,addr_pave: std_logic_vector(6 downto 0);
signal rom_addr: std_logic_vector(13 downto 0);
signal sgreen,sgreen3,sblue3,sblue5,sred5,sgreen5,sred6,sblue6,sgreen6,sred7,sblue7,sgreen7,
sred3,sblue,sred1,sgreen1,sblue1,sred2,sgreen2,sblue2,sblue4,sgreen4,sred4,sred : std_logic;
begin
process(clk_50) begin
if rising_edge(clk_50) then
clk25 <= NOT clk25;
pixel_x1_reg <= pixel_x; -- 2 clock delay
pixel_x2_reg <= pixel_x1_reg;
pixel_y1_reg <= pixel_y;
pixel_y2_reg <= pixel_y1_reg;
end if;
end process;
-- Circuit de synchronisation VGA
vga_sync_unit: vga_sync
port map(clock_25Mhz=>clk25, horiz_sync_out=>hsynch,
vert_sync_out=>vsynch,-- video_on=>video_on,
pixel_column=>pixel_x, pixel_row=>pixel_y);
-- gestion du texte
rom_addr <= char_addr & pixel_y2_reg(4 downto 1) & not pixel_x2_reg(3 downto 1);
font_gen_unit: font_rom
port map(clk=>clk_50, addr => rom_addr, data =>font_bit);
-- doit-on dessiner du texte sur notre écran VGA ?
video_on <=
'1' when (pixel_x<640) and (pixel_y<480) else
'0';
-- doit-on dessiner sur zone caractères ?
text_bit_on <=
'1' when (pixel_y(9 downto 6)="0111") else
'0';
-- sortie rgb
process (video_on,text_bit_on)
begin
if (video_on='0') then
rgb <= "000";
elsif text_bit_on='1' then rgb <= '0' & font_bit & '0'; --vert
else
rgb <= "000";
end if;
end process;
RAMB16_S9_inst : RAMB16_S9_S9 --RAM qui contient le texte à afficher
generic map (
INIT_A => X"000", -- Value of output RAM registers at startup
INIT_B => X"000", -- Value of output RAM registers at startup
SRVAL_A => X"000", -- Ouput value upon SSR assertion
SRVAL_B => X"000", -- Ouput value upon SSR assertion
WRITE_MODE_A => "WRITE_FIRST", -- WRITE_FIRST, READ_FIRST or NO_CHANGE
WRITE_MODE_B => "WRITE_FIRST", -- WRITE_FIRST, READ_FIRST or NO_CHANGE
INITP_00=>X"0000000000000000000000000000000000000000000000000000000000000000",
INITP_01=>X"0000000000000000000000000000000000000000000000000000000000000000",
INITP_02=>X"0000000000000000000000000000000000000000000000000000000000000000",
INITP_03=>X"0000000000000000000000000000000000000000000000000000000000000000",
INITP_04=>X"0000000000000000000000000000000000000000000000000000000000000000",
INITP_05=>X"0000000000000000000000000000000000000000000000000000000000000000",
INITP_06=>X"0000000000000000000000000000000000000000000000000000000000000000",
INITP_07=>X"0000000000000000000000000000000000000000000000000000000000000000",
-- The following INIT_xx declarations specify the intial contents of the RAM
-- Address 0 to 4095
INIT_00=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_01=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_02=>X"56454C202D2030303030203A2045524F43532020205345594F52542D49494547",
INIT_03=>X"000000000000000000000000000000000000000000002020203030203A204C45",
INIT_04=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_05=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_06=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_07=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_08=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_09=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_0a=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_0b=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_0c=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_0d=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_0e=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_0f=>X"0000000000000000000000000000000000000000000000000000000000000000",
-- Address 4096 to 8191
INIT_10=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_11=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_12=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_13=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_14=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_15=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_16=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_17=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_18=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_19=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_1a=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_1b=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_1c=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_1d=>X"00000000000000000000000000000000000000000000000000000000000000000",
INIT_1e=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_1f=>X"0000000000000000000000000000000000000000000000000000000000000000",
-- Address 8192 to 12287
INIT_20=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_21=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_22=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_23=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_24=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_25=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_26=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_27=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_28=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_29=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_2a=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_2b=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_2c=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_2d=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_2e=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_2f=>X"0000000000000000000000000000000000000000000000000000000000000000",
-- Address 12288 to 16383
INIT_30=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_31=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_32=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_33=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_34=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_35=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_36=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_37=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_38=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_39=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_3a=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_3b=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_3c=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_3d=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_3e=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_3f=>X"0000000000000000000000000000000000000000000000000000000000000000")
port map (
DOA(6 downto 0) => char_addr, -- 7-bit Data Output
DOA(7)=> open,
DOPA => open,
ADDRA(6 downto 0) => addr_pave, -- 7-bit Address Input
ADDRA(10 downto 7) => "0000",
CLKA => CLK_50, -- Clock
DIA => "00000000", -- 8-bit Data Input
DIPA => "0",
ENA => '1', -- RAM Enable Input
SSRA => '0', -- Synchronous Set/Reset Input
WEA => '0', -- Write Enable Input
DOB(7 downto 0) => open, -- 8-bit Data Output
DOPB => open,
ADDRB(7 downto 0) => i_AddrL, -- 8-bit Address Input
ADDRB(10 downto 8) => "000", -- not connected
CLKB => CLK_50, -- Clock
DIB => i_Data, -- 8-bit Data Input
DIPB => "0",
ENB => i_CommandBus(0), -- RAM Enable Input
SSRB => '0', -- Synchronous Set/Reset Input
WEB => i_CommandBus(1) -- Write Enable Input
);
addr_pave <= pixel_y(6) & pixel_x(9 downto 4);
--******** fin gestion de l’affichage du texte *****
-- ******* debut de l’affichage graphique *********
balle:rect port map(row=>pixel_y,col=>pixel_x,red1=>sred,green1=>sgreen,blue1=>sblue,colorRGB=>"111",
delta_x=>"0000001010",delta_y=>"0000001100",
x_rec => x_rect, y_rec => y_rect);
bord:rect port map(row=>pixel_y,col=>pixel_x,red1=>sred3,green1=>sgreen3,blue1=>sblue3,colorRGB=>"111",
delta_x=>"1010000000",delta_y=>"0000001000",
x_rec => "0000000000", y_rec => "0110100110");
raquetteG:rect port map(row=>pixel_y,col=>pixel_x,red1=>sred1,green1=>sgreen1,blue1=>sblue1,colorRGB=>"100",
delta_x=>"0000001010",delta_y=>"0000111010",
x_rec => "0000010110", y_rec(8 downto 1) => y_raquG, y_rec(9)=>'0',y_rec(0)=>'0');
raquetteD:rect port map(row=>pixel_y,col=>pixel_x,red1=>sred2,green1=>sgreen2,blue1=>sblue2,colorRGB=>"100",
delta_x=>"0000001010",delta_y=>"0000111010",
x_rec => "1001001000", y_rec(8 downto 1) => y_raquD,y_rec(9)=>'0',y_rec(0)=>'0');
line1:ligne8briques generic map(gx=>420,gy=>0,gdx=>20,gdy=>422)
port map(e_8=>ligne1,segx=>pixel_x,segy=>pixel_y,Sseg_red=>sred6,
Sseg_blue=>sblue6,Sseg_green=>sgreen6);
line2:ligne8briques generic map(gx=>440,gy=>0,gdx=>20,gdy=>422)
port map(e_8=>ligne2,segx=>pixel_x,segy=>pixel_y,Sseg_red=>sred7,
Sseg_blue=>sblue7,Sseg_green=>sgreen7);
red <= sred or sred1 or sred2 or sred3 or sred4 or sred5 or sred6 or sred7 or rgb(2);
green <= sgreen or sgreen1 or sgreen2 or sgreen3 or sgreen4 or sgreen5 or sgreen6 or sgreen7 or rgb(1);
blue <= sblue or sblue1 or sblue2 or sblue3 or sblue4 or sblue5 or sblue6 or sblue7 or rgb(0);
end arch;
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
-- on a ajouté pixel_row et pixel column pour la suite
ENTITY VGA_SYNC IS
PORT( clock_25Mhz : IN STD_LOGIC;
horiz_sync_out, vert_sync_out : OUT STD_LOGIC;
pixel_row, pixel_column: OUT STD_LOGIC_VECTOR(9 DOWNTO 0));
END VGA_SYNC;
ARCHITECTURE aVGA_SYNC OF VGA_SYNC IS
SIGNAL horiz_sync, vert_sync : STD_LOGIC;
SIGNAL h_count, v_count :STD_LOGIC_VECTOR(9 DOWNTO 0);
BEGIN
-- Horiz_sync ------------------------------------__________--------
-- H_count 0 640 651 756 799
-- Bloc compteur du haut et gauche de la figure sur front montant
gestion_H_Count:PROCESS(clock_25Mhz) BEGIN
IF(clock_25Mhz'EVENT) AND (clock_25Mhz='1') THEN
IF (h_count = 799) THEN
h_count <= (others =>'0');
ELSE
h_count <= h_count + 1;
END IF;
END IF;
END PROCESS;
gestion_Horiz_sync: PROCESS(clock_25Mhz,h_count) BEGIN
--Bloc comparateur du haut et à droite de la figure synchronisé sur front descendants
IF(clock_25Mhz'EVENT) AND (clock_25Mhz='0') THEN
IF (h_count <= 751) AND (h_count >= 656) THEN
horiz_sync <= '0';
ELSE
horiz_sync <= '1';
END IF;
END IF;
END PROCESS;
-- Vert_sync -----------------------------------------------_______------------
-- V_count 0 480 493-494 524
-- Bloc compteur en bas à gauche de la figure
gestion_V_Count: PROCESS(clock_25Mhz,h_count) BEGIN
IF(clock_25Mhz'EVENT) AND (clock_25Mhz='1') THEN
IF (v_count >= 524) AND (h_count >= 699) THEN
v_count <= (others =>'0');
ELSIF (h_count = 699) THEN
v_count <= v_count + 1;
END IF;
END IF;
END PROCESS;
gestion_Vertical_sync:PROCESS(clock_25Mhz,v_count) BEGIN
IF(clock_25Mhz'EVENT) AND (clock_25Mhz='0') THEN
--Bloc comparateur du bas et à droite de la figure synchronisé sur front descendants
IF (v_count <= 494) AND (v_count >= 493) THEN
vert_sync <= '0';
ELSE
vert_sync <= '1';
END IF;
END IF;
END PROCESS;
pixel_column <= h_count;
pixel_row <= v_count;
horiz_sync_out <= horiz_sync;
vert_sync_out <= vert_sync;
END aVGA_SYNC;
library IEEE;
use IEEE.STD_LOGIC_1164.all;
Library UNISIM;
use UNISIM.vcomponents.all;
-- RAMB16_S1: Virtex-II/II-Pro, Spartan-3/3E 16kx1 Single-Port RAM
-- Xilinx HDL Libraries Guide, version 10.1.2
entity font_rom is
port(
clk: in std_logic;
addr: in std_logic_vector(13 downto 0);
data: out std_logic_vector(0 downto 0)
);
end font_rom;
architecture arch of font_rom is
begin
RAMB16_S1_inst : RAMB16_S1
generic map (
INIT => X"0", -- Value of output RAM registers at startup
SRVAL => X"0", -- Ouput value upon SSR assertion
WRITE_MODE => "WRITE_FIRST", -- WRITE_FIRST, READ_FIRST or NO_CHANGE
-- The following INIT_xx declarations specify the intial contents of the RAM
-- Address 0 to 4095
INIT_00=>X"000000FF0000FF0000FF0000FF00000000000000000000000000000000000000",
INIT_01=>X"0000242424242424242424242424000000000000FF0000FF0000FF0000FF0000",
INIT_02=>X"0000929292929292929292929292000000004949494949494949494949490000",
INIT_03=>X"0000AAAAAAAAAAAAAAAAAAAAAAAA000000005555555555555555555555550000",
INIT_04=>X"0000F3FCF3FCF3FCF3FCF3FCF3FC00000000FF00FF00FF00FF00FF00FF000000",
INIT_05=>X"00000C030C030C030C030C030C0300000000CF3FCF3FCF3FCF3FCF3FCF3F0000",
INIT_06=>X"00000066666666000066666666000000000030C030C030C030C030C030C00000",
INIT_07=>X"00000F0F0F0F0F0F0F0F0F0F0F0F00000000FF99999999FFFF99999999FF0000",
INIT_08=>X"0000000000000000FFFFFFFFFFFF00000000F0F0F0F0F0F0F0F0F0F0F0F00000",
INIT_09=>X"00000F0F0F0F0F0F0F0F0F0F0F0F00000000FFFFFFFFFFFF0000000000000000",
INIT_0a=>X"0000007E42424242424242427E0000000000F0F0F0F0F0F0F0F0F0F0F0F00000",
INIT_0b=>X"000024492449244924492449244900000000FF81818181818181818181FF0000",
INIT_0c=>X"0000499249924992499249924992000000002492249224922492249224920000",
INIT_0d=>X"0000AA55AA55AA55AA55AA55AA550000000055AA55AA55AA55AA55AA55AA0000",
INIT_0e=>X"0000DB6DDB6DDB6DDB6DDB6DDB6D00000000DBB6DBB6DBB6DBB6DBB6DBB60000",
INIT_0f=>X"0000FFFFFFFFFFFFFFFFFFFFFFFF00000000B66DB66DB66DB66DB66DB66D0000",
-- Address 4096 to 8191
INIT_10=>X"0000001000001010101010101010000000000000000000000000000000000000",
INIT_11=>X"0000004444FE4444444444FE4444000000000000000000000044444444440000",
INIT_12=>X"0000000C12924C2010086492906000000000007C921212127C909090927C0000",
INIT_13=>X"000000000000000000101010101000000000007A84848A507090888848300000",
INIT_14=>X"0000001008080404040404080810000000000010202040404040402020100000",
INIT_15=>X"0000000010101010FE101010100000000000009292545438FE38545492920000",
INIT_16=>X"0000000000000000FE0000000000000000000020100808000000000000000000",
INIT_17=>X"0000000000804020100804020000000000000000001818000000000000000000",
INIT_18=>X"0000003810101010101010503010000000000038448282A2928A828244380000",
INIT_19=>X"0000007C820202027C020202827C0000000000FE808080807C020202827C0000",
INIT_1a=>X"0000007C820202027C80808080FE00000000001C080808FE8888482818080000",
INIT_1b=>X"00000038101010101008040202FE00000000007C828282827C808080807E0000",
INIT_1c=>X"000000FC020202027C828282827C00000000007C828282827C828282827C0000",
INIT_1d=>X"0000002010080800000018180000000000000000001818000000181800000000",
INIT_1e=>X"0000000000FE0000000000FE000000000000000000020C30C0300C0200000000",
INIT_1f=>X"0000001000101008040282824438000000000000008060180618608000000000",
-- Address 8192 to 12287
INIT_20=>X"00000082828244447C442828281000000000003C42809EA2A29E828244380000",
INIT_21=>X"0000007C8280808080808080827C0000000000FC82828284F884828282FC0000",
INIT_22=>X"000000FE80808080FC80808080FE0000000000F8848482828282848488F00000",
INIT_23=>X"0000007C828282829E808080827C00000000008080808080FC80808080FE0000",
INIT_24=>X"0000003810101010101010101038000000000082828282827C82828282820000",
INIT_25=>X"0000008282848488F088848482820000000000708888080808080808081C0000",
INIT_26=>X"000000828282829292AAAAAAC6820000000000FE808080808080808080800000",
INIT_27=>X"0000007C8282828282828282827C000000000082868A8A8A92A2A2A2C2820000",
INIT_28=>X"0000007A848AB28282828282827C00000000008080808080FC828282827C0000",
INIT_29=>X"0000007C820202027C808080827C000000000082848890A0FC828282827C0000",
INIT_2a=>X"0000007C82828282828282828282000000000010101010101010101092FE0000",
INIT_2b=>X"00000082C6AAAAAA929282828282000000000010102828284444448282820000",
INIT_2c=>X"0000001010101010282844448282000000000082824444283828444482820000",
INIT_2d=>X"00000038202020202020202020380000000000FE824040203808040482FE0000",
INIT_2e=>X"0000003808080808080808080838000000000000000204081020408000000000",
INIT_2f=>X"000000FE00000000000000000000000000000000000000000082442810000000",
-- Address 12288 to 16383
INIT_30=>X"0000003AC6828282C63A00000000000000000000000000000008101020200000",
INIT_31=>X"0000003CC2808080C23C000000000000000000B8C6828282C6B8808080800000",
INIT_32=>X"00000038C680FC82C6380000000000000000003AC6828282C63A020202020000",
INIT_33=>X"00000038C6027E82C638000000000000000000808080808080F88080423C0000",
INIT_34=>X"0000000C1210101010100000100000000000008282828282C6B8808080800000",
INIT_35=>X"000000828488B0C0B88680808000000000000070880404040404000004000000",
INIT_36=>X"0000009292929292D2AC0000000000000000000E102020202020202020200000",
INIT_37=>X"00000038C6828282C6380000000000000000008282828282C6B8000000000000",
INIT_38=>X"0000000202027E82C63A000000000000000000808080FC82C6B8000000000000",
INIT_39=>X"0000007C82027E80827C0000000000000000008080808080C6B8000000000000",
INIT_3a=>X"0000003AC682828282820000000000000000003C4280808080F8808080800000",
INIT_3b=>X"0000006C92929292928200000000000000000010284482828282000000000000",
INIT_3c=>X"0000003008083C42820000000000000000000082443828448200000000000000",
INIT_3d=>X"00000010202020408040202020100000000000FE40300804FE00000000000000",
INIT_3e=>X"0000001008080804020408080810000000000010101010101010101010100000",
INIT_3f=>X"00000000000000000000000000000000000000000000000C9260000000000000")
port map (
DO => data, -- 1-bit Data Output
ADDR => ADDR, -- 14-bit Address Input
CLK => CLK, -- Clock
DI => "0", -- 1-bit Data Input
EN => '1', -- RAM Enable Input
SSR => '0', -- Synchronous Set/Reset Input
WE => '0' -- Write Enable Input
);
-- End of RAMB16_S1_inst instantiation
end arch;
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
--use ieee.numeric_std.all;
ENTITY rect IS PORT( --ancien rectangle
row,col,x_rec,y_rec,delta_x,delta_y :in STD_LOGIC_VECTOR(9 DOWNTO 0);
colorRGB : in STD_LOGIC_VECTOR(2 DOWNTO 0);
red1,green1,blue1 : out std_logic);
END rect;
ARCHITECTURE arect of rect is begin
PROCESS(row,col,x_rec,y_rec) BEGIN
if row > y_rec and row < y_rec+delta_y then
if col >x_rec and col < x_rec+delta_x then
red1 <= colorRGB(2);
green1 <= colorRGB(1);
blue1 <= colorRGB(0);
else
red1 <= '0';
green1 <= '0';
blue1 <= '0';
end if;
else
red1 <= '0';
green1 <= '0';
blue1 <= '0';
end if;
end process;
end arect;
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
-- nouveau rectangle avec les generic
ENTITY rect_generic is
generic(x,y,dx,dy : natural);
port(vgax, vgay : in std_logic_vector(9 downto 0);
RGB : in std_logic_vector(2 downto 0);
e_rect : in std_logic;
s_red, s_blue, s_green : out std_logic);
END rect_generic;
ARCHITECTURE arect_gen OF rect_generic IS
BEGIN
PROCESS (vgax, vgay, e_rect)
BEGIN
IF vgax>x AND vgax<x+dx THEN
IF vgay>y and vgay<y+dy THEN
IF e_rect = '1' THEN
s_red <= RGB(0);
s_green <= RGB(1);
s_blue <= RGB(2);
ELSE
s_red <= '0';
s_green <= '0';
s_blue <= '0';
END IF;
ELSE
s_red <= '0';
s_green <= '0';
s_blue <= '0';
END IF;
ELSE
s_red <= '0';
s_green <= '0';
s_blue <= '0';
END IF;
END PROCESS;
END arect_gen;
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
-- affichage complet mur 8 briques
entity ligne8briques is
generic (gx, gy, gdx, gdy : natural); --gx, gy = position ; gdx, gdy = tailles
port(e_8 : in std_logic_vector(7 downto 0); -- valeur à affichier
segx, segy : in std_logic_vector(9 downto 0);-- où en est le balayage de l'écran ?
Sseg_red, Sseg_green, Sseg_blue : out std_logic); -- signaux de couleurs
end ligne8briques;
architecture a_ligne8briques of ligne8briques is
component rect_generic
generic(x,y,dx,dy : natural);
port(vgax, vgay : in std_logic_vector(9 downto 0);
RGB : in std_logic_vector(2 downto 0);
e_rect : in std_logic;
s_red, s_blue, s_green : out std_logic);
end component;
signal sig_aff_red, sig_aff_green, sig_aff_blue : std_logic_vector(7 downto 0);
begin
i1 : rect_generic generic map (x=>gx+1, y=> gy+1, dx =>gdx-2, dy=>(gdy-gy)/8-2)
port map (RGB=>"100", e_rect=>e_8(0), vgax=>segx, vgay=>segy,
s_red=>sig_aff_red(0), s_green=>sig_aff_green(0), s_blue=>sig_aff_blue(0));
i2 : rect_generic generic map (x=>gx+1, y=> gy+((gdy-gy)/8), dx =>gdx-2, dy=>(gdy-gy-2)/8)
port map (RGB=>"100", e_rect=>e_8(1), vgax=>segx, vgay=>segy,
s_red=>sig_aff_red(1), s_green=>sig_aff_green(1), s_blue=>sig_aff_blue(1));
i3 : rect_generic generic map (x=>gx+1, y=> gy+(2*(gdy-gy)/8), dx =>gdx-2, dy=>(gdy-gy-2)/8)
port map (RGB=>"100", e_rect=>e_8(2), vgax=>segx, vgay=>segy,
s_red=>sig_aff_red(2), s_green=>sig_aff_green(2), s_blue=>sig_aff_blue(2));
i4 : rect_generic generic map (x=>gx+1, y=> gy+(3*(gdy-gy)/8), dx =>gdx-2, dy=>(gdy-gy-2)/8)
port map (RGB=>"100", e_rect=>e_8(3), vgax=>segx, vgay=>segy,
s_red=>sig_aff_red(3), s_green=>sig_aff_green(3), s_blue=>sig_aff_blue(3));
i5 : rect_generic generic map (x=>gx+1, y=> gy+(4*(gdy-gy)/8), dx =>gdx-2, dy=>(gdy-gy-2)/8)
port map (RGB=>"100", e_rect=>e_8(4), vgax=>segx, vgay=>segy,
s_red=>sig_aff_red(4), s_green=>sig_aff_green(4), s_blue=>sig_aff_blue(4));
i6 : rect_generic generic map (x=>gx+1, y=> gy+(5*(gdy-gy)/8), dx =>gdx-2, dy=>(gdy-gy-2)/8)
port map (RGB=>"100", e_rect=>e_8(5), vgax=>segx, vgay=>segy,
s_red=>sig_aff_red(5), s_green=>sig_aff_green(5), s_blue=>sig_aff_blue(5));
i7 : rect_generic generic map (x=>gx+1, y=> gy+(6*(gdy-gy)/8), dx =>gdx-2, dy=>(gdy-gy-2)/8)
port map (RGB=>"100", e_rect=>e_8(6), vgax=>segx, vgay=>segy,
s_red=>sig_aff_red(6), s_green=>sig_aff_green(6), s_blue=>sig_aff_blue(6));
i8 : rect_generic generic map (x=>gx+1, y=> gy+(7*(gdy-gy)/8), dx =>gdx-2, dy=>(gdy-gy-2)/8)
port map (RGB=>"100", e_rect=>e_8(7), vgax=>segx, vgay=>segy,
s_red=>sig_aff_red(7), s_green=>sig_aff_green(7), s_blue=>sig_aff_blue(7));
Sseg_red<= sig_aff_red(0) or sig_aff_red(1) or sig_aff_red(2) or sig_aff_red(3) or sig_aff_red(4)
or sig_aff_red(5) or sig_aff_red(6) or sig_aff_red(7);
Sseg_green<= sig_aff_green(0) or sig_aff_green(1) or sig_aff_green(2) or sig_aff_green(3) or sig_aff_green(4)
or sig_aff_green(5) or sig_aff_green(6) or sig_aff_green(7);
Sseg_blue<= sig_aff_blue(0) or sig_aff_blue(1) or sig_aff_blue(2) or sig_aff_blue(3) or sig_aff_blue(4)
or sig_aff_blue(5) or sig_aff_blue(6) or sig_aff_blue(7);
end a_ligne8briques;
Partie logicielle
modifierEn 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.
// gestion d'un casse brique dans un FPGA = VGA+ATMega8
// Ce programme tient dans environ {{Unité|2|{{Abréviation|ko|kilooctet}}}} sur {{Unité|8|{{Abréviation|ko|kilooctet}}}} dispo
// Il manque cependant de jouabilité !!!!
#include <avr/io.h>
#include "util/delay.h"
#undef F_CPU
#define F_CPU 25000000UL
/* Port A */
//#define PINA _SFR_IO8(0x19)
// Les ports ci-dessous n'existent pas dans l'ATMega8 de la vraie vie mais nous les avons ajouté dans notre cœur
// d'où leur présence ici : ne pas modifier les fichiers d'entête !
#define DDRA _SFR_IO8(0x1A)
#define PORTA _SFR_IO8(0x1B)
//******************************************************************************************************************************
// constants definitions (see VHDL files)
//******************************************************************************************************************************
// Constantes liées au matériel (voir dans VHDL)
// Mes étudiants, par exemple, ont mis les deux murs plus à gauche ce qui change ces constantes
//************** wall 1
#define GX1 420
#define GY 0
#define GDX 20
#define GDY 422
//************** wall 2
#define GX2 440
//******************************************************************************************************************************
// data type definitions and the correspionding arrays
//******************************************************************************************************************************
//L'utilisation pratique des tableaux ci-dessous nous a montré qu’il est possible de retirer le champ xdeb pour économiser de la place mémoire
struct s_brique {
uint16_t xdeb,ydeb,xfin,yfin;
} tabmur1[8]={{GX1-10,GY,GX1+GDX,(GDY-GY)/8},{GX1-10,(GDY-GY)/8 -12,GX1+GDX,2*(GDY-GY)/8},{GX1-10,2*(GDY-GY)/8 -12,GX1+GDX,3*(GDY-GY)/8},
{GX1-10,3*(GDY-GY)/8 -12,GX1+GDX,4*(GDY-GY)/8},{GX1-10,4*(GDY-GY)/8 -12,GX1+GDX,5*(GDY-GY)/8},{GX1-10,5*(GDY-GY)/8 -12,GX1+GDX,6*(GDY-GY)/8},
{GX1-10,6*(GDY-GY)/8 -12,GX1+GDX,7*(GDY-GY)/8},{GX1-10,7*(GDY-GY)/8 -12,GX1+GDX,(GDY-GY)}},
tabmur2[8]={{GX2-10,GY,GX2+GDX,(GDY-GY)/8},{GX2-10,(GDY-GY)/8 -12,GX2+GDX,2*(GDY-GY)/8},{GX2-10,2*(GDY-GY)/8 -12,GX2+GDX,3*(GDY-GY)/8},
{GX2-10,3*(GDY-GY)/8 -12,GX2+GDX,4*(GDY-GY)/8},{GX2-10,4*(GDY-GY)/8 -12,GX2+GDX,5*(GDY-GY)/8},{GX2-10,5*(GDY-GY)/8 -12,GX2+GDX,6*(GDY-GY)/8},
{GX2-10,6*(GDY-GY)/8 -12,GX2+GDX,7*(GDY-GY)/8},{GX2-10,7*(GDY-GY)/8 -12,GX2+GDX,(GDY-GY)}};
//******************************************************************************************************************************
// functions prototypes
//******************************************************************************************************************************
void setXY(uint16_t x,unsigned int y);
void My_delay(unsigned int delay);
void bounceOffTheWall(unsigned int posX,unsigned int posY,unsigned char level,unsigned char levelMin,
struct s_brique tableauData[],signed char *deltaX,signed char *deltaY,unsigned char *mur);
unsigned char PseudoAleat(uint16_t Lim);
void putLevel(uint8_t level);
void putScore(uint16_t Score);
//******************************************************************************************************************************
// main
//******************************************************************************************************************************
int main (void)
{
unsigned int posRaqu_16;
unsigned int posX,posY,tempo=250; // change to 200 to increase speed
unsigned char raqD_y=0,raqG_y=0,dizG=0,level=1,line1=0,line2=0,random=0;
signed char deltaX=1,deltaY=1,delta_raqG_y=1;
// int err = (dx>dy ? dx : -dy)/2, e2; montre comment calculer err
int dx=10,dy=5,err=5,e2; //Pour Bresenham
int nbRebondsGauches=0,Score= 0x0000, increment;
line1 = 0xFF; // level 1
PORTA = line1; //update the wall number 1
DDRA = 0x00; // update the wall number 2
while(1) {
putLevel(level); // two digits level
// starting position
posX=320;
posY=200;
setXY(posX,posY);
deltaX=-1;
//attente du départ et recherche graine du gene pseudoaléatoire
while (bit_is_clear(PINB,PINB0))random = PseudoAleat(12);
while (posX>30 && (line1 || line2)) { //equation booléenne pas facile à trouver
posRaqu_16=raqD_y<<1;
if (posX>=574) { // le rebond est automatique
//Ne pas remettre le test ci-dessous semble poser problème de temps en temps
if (deltaX>0) deltaX= -deltaX; // rebond sur raquette droite
// changement de pente aléatoire
dy = PseudoAleat(12);
}
posRaqu_16=raqG_y<<1; //rebond sur raquette gauche ?
if ((posX<=32) && (posY<posRaqu_16+58) && (posY+10>posRaqu_16) && (deltaX<0)) {
deltaX= -deltaX; // rebond sur raquette gauche
nbRebondsGauches++;
if (delta_raqG_y) { // delta_raqG_y!=0 on change la pente
if ((delta_raqG_y >0 && deltaY >0) || (delta_raqG_y <0 && deltaY <0)) {
dy+=5; if (dy > 15) dy=15; // ne peut dépasser 15
} else {
dy-=5; if (dy < 5) dy=5; // ne peut dépasser 5
}
}
}
// rebond sur mur du bas
if ((posY>=417) && (deltaY>0)) deltaY= -deltaY;
// rebond sur haut de l'écran
if ((posY<=10) && (deltaY<0)) deltaY= -deltaY;
//*********** calcul du rebond sur mur1
bounceOffTheWall(posX,posY,level,3,tabmur1,&deltaX,&deltaY,&line1);
//*********** calcul du rebond sur mur2
bounceOffTheWall(posX,posY,level,6,tabmur2,&deltaX,&deltaY,&line2);
//*********** Début de Brensenham
e2 = err;
if (e2 >-dx) { err -= dy; posX += deltaX; }
if (e2 < dy) { err += dx; posY += deltaY; }
setXY(posX,posY);
//*********** fin de Bresenham
// gestion des raquettes 2bits PORTB/raquette
delta_raqG_y=0;
if (bit_is_set(PINB,PINB6)) if (raqG_y<182) delta_raqG_y=1;
if (bit_is_set(PINB,PINB7)) if (raqG_y>0) delta_raqG_y=-1;
raqG_y = raqG_y + delta_raqG_y;
PORTC=raqG_y;
raqD_y = posY>>1;
if (raqD_y>182) raqD_y=182;
DDRC=raqD_y;
// you want to see the walls :
PORTA = line1; //update the wall number 1
DDRA = line2; //update the wall number 2
My_delay(tempo);
} // end of while (posX>30&& (line1 || line2)) : fin des niveaux
// gestion des scores
if ((line1==0) && (line2==0)) {
increment = 12 - (nbRebondsGauches>>3) ; // formule à modifier en fonction des levels
if (increment < 1) increment = 1;
Score = Score + increment;
nbRebondsGauches=0;
// passage score en BCD
if ((Score & 0x000F)>9) Score = Score + 6;
if ((Score & 0x00F0)>0x0090) Score = Score + 0x0060;
if ((Score & 0x0F00)>0x0900) Score = Score + 0x0600;
if ((Score & 0xF000)>0x9000) Score = 0x0001; // Overflow!!! too late
// affichage du score
putScore(Score);
} else nbRebondsGauches += 10; // si perdu equivalent à 10 rebonds gauches
if (posX>30) {// remove for easier checks
level++;
// remove the comment below if you want speed increases with level
// tempo -=20;
// managing level with 2 digits in BCD
if ((level & 0X0F) > 9) {
dizG++;
level = dizG <<4;
} // end if
if (dizG > 9) {dizG=0;level=0;}
} // end if
if (level== 3) line1 = 0xFF; // mur 1
if (level == 2||level== 4) line1 = 0xAA;
if (level >= 5) {
line2 = 0xFF; // ligne2
line1 = 0xFF; // ligne1
}
//*********************************************************
// Rien de particulier n'est fait à partir du niveau 7 !!!!
// On pourrait frapper plusieurs coups pour éteindre une brique
// avec changement de couleurs... Voir futur projet 2012/2013
//*********************************************************
// updating walls
PORTA = line1; //update the wall number 1
DDRA = line2; //update the wall number 2
} //end while(1)
} //end main
//******************************************************************************************************************************
// function setXY()
// purpose: put the ball with x and y coordinates
// arguments:
// corresponding x and y coordinates
// return:
// note:
//******************************************************************************************************************************
void setXY(uint16_t x,unsigned int y){
DDRD=x; //least significant byte
PORTD=x>>8;//most significant byte
DDRB=y; //least significant byte
PORTB=y>>8;//most significant byte
}
//******************************************************************************************************************************
// function PseudoAleat()
// purpose: simple random generator
// arguments:
// corresponding modulo = nb different values :12 for us
// return:
// note:
//******************************************************************************************************************************
unsigned char PseudoAleat(uint16_t Lim){
static uint16_t Y = 1;
Y = (Y * 32719 + 3) % 32749;
return ((Y % Lim) + 4); // offset de 4
}
//******************************************************************************************************************************
// function My_delay()
// purpose: time consuming function to waste time
// arguments:
// corresponding delay
// return:
// note:
//******************************************************************************************************************************
void My_delay(unsigned int delay){
int i;
for(i=0;i<delay;i++) _delay_ms(1);
}
//******************************************************************************************************************************
// function putLevel()
// purpose: put level in the screen by writing in ROM
// arguments:
// corresponding level
// return:
// note: WEB est en b1 et ENB en b0 tandis que SSRB est forcé matériellement à 0
//******************************************************************************************************************************
void putLevel(uint8_t level){
uint8_t Data;
Data = level & 0x0F;
ADCH = 0;
TWAR = 102;
ADCL = Data + '0';
ADCH = 3;
ADCH = 0;
Data = level & 0xF0;
Data >>= 4;
TWAR = 101;
ADCL = Data + '0';
ADCH = 3;
ADCH = 0;
}
//******************************************************************************************************************************
// function putSore()
// purpose: put level in the screen by writing in ROM
// arguments:
// corresponding Score
// return:
// note: WEB est en b1 et ENB en b0 tandis que SSRB est forcé matériellement à 0
//******************************************************************************************************************************
void putScore(uint16_t Score){
uint8_t Data;
Data = Score & 0x000F;
ADCH = 0;
TWAR = 89;
ADCL = Data + '0';
ADCH = 3;
ADCH = 0;
Data = Score & 0x00F0;
Data >>= 4;
TWAR = 88;
ADCL = Data + '0';
ADCH = 3;
ADCH = 0;
Data = Score & 0x0F00;
Data >>= 8;
TWAR = 87;
ADCL = Data + '0';
ADCH = 3;
ADCH = 0;
Data = Score & 0xF000;
Data >>= 12;
TWAR = 86;
ADCL = Data + '0';
ADCH = 3;
ADCH = 0;
}
//******************************************************************************************************************************
// function bounceOffTheWall()
// purpose: to bounce off the wall if necessary
// arguments:
// corresponding coordinates of the ball : posX and posY
// level : the current level of the game
// levelMin : from this level on, only a left bounce can destroy a break
// tableauData : data array of the corresponding wall under investigation
// current and calculated coordinates differences : *delatX,*deltaY
// current and calculated bits for the breaks : mur (wall in English)
// return:
// note:
//******************************************************************************************************************************
void bounceOffTheWall(unsigned int posX,unsigned int posY,unsigned char level,unsigned char levelMin,
struct s_brique tableauData[],signed char *deltaX,signed char *deltaY,unsigned char *mur) {
//levelMin = 3 pour premier mur, = 6 pour deuxieme mur
unsigned char i; // compteur de boucle
//tableauData[0].xdeb=GX1-10
if (posX==tableauData[0].xdeb && level > 0 && *mur != 0) // rebond avec effacement
for(i=0;i<8;i++)
if (posY>=tableauData[i].ydeb && posY <= tableauData[i].yfin && (*mur & 1<<i) && *deltaX >0) {
*deltaX = -*deltaX; // rebond
*mur &= ~(1<<i); // on éteint la brique
}
if (posX==tableauData[0].xfin && level > 0) // rebond parfois sans effacement
for(i=0;i<8;i++)
if (posY>=tableauData[i].ydeb && posY <= tableauData[i].yfin && (*mur & 1<<i) && *deltaX <0) {
*deltaX = -*deltaX; // rebond
if (level < levelMin)
*mur &= ~(1<<i); // on efface la brique
}
if (posX > (tableauData[0].xdeb) && posX < (tableauData[0].xfin) && level > 0) { // possibilité de rebond vertical sur brique
if( *deltaY > 0) { // rebond par le haut
for(i=1;i<8;i++) //pas brique du haut
if (posY == tableauData[i].ydeb && (*mur & 1<<i) ) { //tab[i].ydeb contient déjà le -12
*deltaY = -*deltaY; // rebond
if (level < levelMin)
*mur &= ~(1<<i); // on efface la brique
}
} else //autrement change de signe sans arret
// rebond par le bas
for(i=0;i<7;i++) //pas brique du bas
if (posY == tableauData[i].yfin && (*mur & 1<<i) ) {
*deltaY = -*deltaY; // rebond
if (level < levelMin)
*mur &= ~(1<<i); // on efface la brique
}
}
}
Projet Pong et téléphones portables (2012/2013)
modifierIl 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
modifierOn 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
modifierDeux 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
modifierLe 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
modifierLa 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
modifierLes 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
modifierLa 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
modifierVoici 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.
# ==== Clock inputs (CLK) ==== NET I_CLK_50 PERIOD = 20 ns; NET I_CLK_50 TNM_NET = I_CLK_100; NET "I_CLK_50" LOC = "C9" | IOSTANDARD = LVCMOS33 ; #NET "clk" LOC = "C9" | IOSTANDARD = LVCMOS33 ; # ==== Discrete LEDs (LED) ==== # These are shared connections with the FX2 connector #NET "LED<0>" LOC = "F12" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 8 ; #NET "LED<1>" LOC = "E12" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 8 ; #NET "LED<2>" LOC = "E11" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 8 ; #NET "LED<3>" LOC = "F11" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 8 ; #NET "LED<4>" LOC = "C11" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 8 ; #NET "LED<5>" LOC = "D11" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 8 ; #NET "LED<6>" LOC = "E9" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 8 ; #NET "LED<7>" LOC = "F9" | IOSTANDARD = LVTTL | SLEW = SLOW | DRIVE = 8 ; # ==== Slide Switches (SW) ==== #NET "SW<0>" LOC = "L13" | IOSTANDARD = LVTTL | PULLUP ; #NET "SW<1>" LOC = "L14" | IOSTANDARD = LVTTL | PULLUP ; #NET "SW<2>" LOC = "H18" | IOSTANDARD = LVTTL | PULLUP ; #NET "SW<3>" LOC = "N17" | IOSTANDARD = LVTTL | PULLUP ; # ==== Pushbuttons (BTN) ==== #NET "BTN_EAST" LOC = "H13" | IOSTANDARD = LVTTL | PULLDOWN ; #NET "BTN_NORTH" LOC = "V4" | IOSTANDARD = LVTTL | PULLDOWN ; NET "I_CLR" LOC = "K17" | IOSTANDARD = LVTTL | PULLDOWN ; #NET "BTN_WEST" LOC = "D18" | IOSTANDARD = LVTTL | PULLDOWN ; # ==== RS-232 Serial Ports (RS232) ==== NET "I_RX" LOC = "R7" | IOSTANDARD = LVTTL ; NET "Q_TX" LOC = "M14" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = SLOW ; #NET "RS232_DTE_RXD" LOC = "U8" | IOSTANDARD = LVTTL ; #NET "RS232_DTE_TXD" LOC = "M13" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = SLOW ; # ==== VGA Port (VGA) ==== NET "blue" LOC = "G15" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ; NET "green" LOC = "H15" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ; NET "hsynch" LOC = "F15" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ; NET "red" LOC = "H14" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ; NET "vsynch" LOC = "F14" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ; INST cpu/opcf/pmem/pe_1 LOC = RAMB16_X1Y2; INST cpu/opcf/pmem/pe_0 LOC = RAMB16_X0Y3; INST cpu/opcf/pmem/pe_3 LOC = RAMB16_X1Y3; INST cpu/opcf/pmem/pe_2 LOC = RAMB16_X0Y2; INST cpu/opcf/pmem/po_1 LOC = RAMB16_X1Y0; INST cpu/opcf/pmem/po_0 LOC = RAMB16_X0Y0; INST cpu/opcf/pmem/po_3 LOC = RAMB16_X1Y1; INST cpu/opcf/pmem/po_2 LOC = RAMB16_X0Y1;
Éléments de solution Android
modifierUn 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
package com.pong;
import android.app.TabActivity;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.widget.TabHost;
import android.app.*;
import android.view.*;
import android.widget.*;
public class MainActivity extends TabActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Resources res = getResources();
TabHost tabHost = getTabHost();
TabHost.TabSpec spec;
Intent intent;
intent = new Intent().setClass(this, Gyroscope.class);
spec = tabHost.newTabSpec("Gyroscope").setIndicator("Gyroscope").setContent(intent);
tabHost.addTab(spec);
intent = new Intent().setClass(this, Boutons.class);
spec = tabHost.newTabSpec("Boutons").setIndicator("Boutons").setContent(intent);
tabHost.addTab(spec);
}
}
- Le fichier Boutons.java
package com.pong;
import android.app.*;
import android.bluetooth.*;
import android.os.*;
import android.view.*;
import android.view.View.*;
import android.widget.*;
import java.io.*;
import java.util.*;
public class Boutons extends Activity
{
//private InputStream receiveStream = null;
private OutputStream sendStream = null;
private String descendre = "d";
private String monter = "u";
private BluetoothDevice device = null;
private BluetoothSocket socket = null;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.boutons);
// connexion();
final Button quitter = (Button) findViewById(R.id.monter);
quitter.setOnTouchListener(new OnTouchListener(){
public boolean onTouch(View p1, MotionEvent p2)
{
monter();
return false;
}
});
//fin action quitter
final Button quitter2 = (Button) findViewById(R.id.descendre);
quitter2.setOnTouchListener(new OnTouchListener(){
public boolean onTouch(View p3, MotionEvent p4)
{
descendre();
return false;
}
});
}
public void connexion() {
Set<BluetoothDevice> setpairedDevices = BluetoothAdapter.getDefaultAdapter().getBondedDevices();
BluetoothDevice[] pairedDevices = (BluetoothDevice[]) setpairedDevices.toArray(new BluetoothDevice[setpairedDevices.size()]);
for(int i=0;i<pairedDevices.length;i++) {
if(pairedDevices[i].getName().contains("TEST")) {
device = pairedDevices[i];
try {
socket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
socket.connect();
sendStream = socket.getOutputStream();
// Toast.makeText(Boutons.this,"connexion OK",Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
public void monter() {
try {
sendStream.write(monter.getBytes());
sendStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public void descendre() {
try {
sendStream.write(descendre.getBytes());
sendStream.flush();
} catch(IOException e) {
e.printStackTrace();
}
}
}
- fichier Gyroscope.java
package com.pong;
import android.app.*;
import android.bluetooth.*;
import android.graphics.*;
import android.hardware.*;
import android.os.*;
import android.widget.*;
import java.io.*;
import java.text.*;
import java.util.*;
import android.view.*;
import android.app.ActionBar;
public class Gyroscope extends Activity implements SensorEventListener
{
//bluetooth
private OutputStream sendStream = null;
private String descendre = "d";
private String monter = "u";
private BluetoothDevice device = null;
private BluetoothSocket socket = null;
private int p;
//gyroscope
private float x, y, z,w;
private float range;
private SensorManager sensormanager;
private Sensor gyroscope;
private TextView valeur;
private TextView connex;
@Override
public void onSensorChanged(SensorEvent event)
{
if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
x = event.values[0];
y = event.values[1];
z = event.values[2];
// inclure delai ici ?
maj();
// move();
w = (455*y) + 87;
p =(int)w;
if (p<0) {
p=0;
}
if (p>178) {
p=178;
}
try {
// connexion();
sendStream.write(p);
sendStream.flush();
}
catch (IOException e)
{
}
}
}
public void move() {
try
{
sendStream.write(p);
sendStream.flush();
}
catch (IOException e)
{}
}
@Override
public void onAccuracyChanged(Sensor sensor, int precision)
{
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.gyrocope);
sensormanager = (SensorManager) getSystemService(SENSOR_SERVICE);
gyroscope = sensormanager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
range = gyroscope.getMaximumRange();
valeur = (TextView) findViewById(R.id.txtView);
connex = (TextView) findViewById(R.id.txtconnect);
connex.setTextColor(Color.RED);
connex.setText("Vous êtes déconnecter");
connexion();
}
public void connexion() {
Set<BluetoothDevice> setassocier = BluetoothAdapter.getDefaultAdapter().getBondedDevices();
BluetoothDevice[] associer = (BluetoothDevice[]) setassocier.toArray(new BluetoothDevice[setassocier.size()]);
for(int i=0;i<associer.length;i++) {
if(associer[i].getName().contains("TEST")) {
device = associer[i];
try {
socket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
socket.connect(); // à mettre dans un thread plutard
sendStream = socket.getOutputStream();
// Toast.makeText(MainActivity.this,"connexion OK",Toast.LENGTH_SHORT).show();
connex.setTextColor(Color.GREEN);
connex.setText("Connexion établie");
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
@Override
protected void onPause() {
sensormanager.unregisterListener(this,gyroscope);
super.onPause();
}
@Override
protected void onResume() {
sensormanager.registerListener(this, gyroscope,SensorManager.SENSOR_DELAY_UI);
super.onResume();
}
private void maj() {
DecimalFormat f = new DecimalFormat("#.##"); // modifier le format
String message = String.format(getString(R.string.valeur), f.format(x), f.format(y), f.format(z),f.format(p));
valeur.setText(message);
}
public void deconnexion() {
try {
socket.close();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
Projet pacman pour 2012/2013
modifierLa 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
modifierL'é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
modifierRappelons à 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
modifierTout 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
modifierUn 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
modifierNous 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 :
À 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
modifierEn 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.
Finalement le mini joystick est abandonné au profit d'une manette Nunchuk de la console Wii de Nintendo. Il s'agit d'un protocole I2C pas très difficile à mettre en œuvre. Nous avons donc ajouté un chapitre à ce livre pour l’occasion : Les nouvelles interfaces : de la Nunchuk de Nitendo à Android.
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
modifierOn 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 :
- 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.
- 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.
- 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
modifierOn 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...
Pour des raisons indépendantes de notre volonté nous avons été obligé de ne pas laisser les étudiants faire ce travail. Nous voulions en effet avoir le pacman fonctionnant avec la manette Nunchuk à mi-projet pour pouvoir le présenter au 40° anniversaire de notre département Génie Électrique. Nous avons cependant laissé du travail à faire pour les futures années (récupération des données accéléromètre).
Un chapitre a été ajouté à ce livre qui donne une solution pour la partie i2c. Nous n'avons pas utilisé le projet de Eli Smertenko et avons abandonné l’idée de gérer l'i2c comme dans l'ATMega8 original.
Correction partielle
modifierNous allons donner la correction partielle de ce projet.
La partie matérielle
modifierRappelons 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é :
Un coup d’œil cependant vous montrera qu’il manque l'essentiel : le programme C que nous allons présenter maintenant.
La partie logicielle
modifierPour 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// gestion d'un pacman dans un FPGA = VGA+ATMega8
// Serge Moutou 2012/2013
#include <avr/io.h>
#undef F_CPU
#define F_CPU 25000000UL
#include "util/delay.h"
/* Port A */
//#define PINA _SFR_IO8(0x19)
// Les ports ci-dessous n'existent pas dans l'ATMega8 de la vraie vie mais nous les avons ajouté dans notre cœur
// d'où leur présence ici : ne pas modifier les fichiers d'entête !
#define DDRA _SFR_IO8(0x1A)
#define PORTA _SFR_IO8(0x1B)
//**********************************************************************************************************
// constants definitions (see VHDL files)
//**********************************************************************************************************
#define SCOREUNIT 1945+1024+21+900 //debut memoire(2048)+2*derniere ligne(900)+2*offset dans ligne(21)
#define LEVELUNIT 1958+1024+34+900 //debut memoire(2048)+2*derniere ligne(900)+2*offset dans ligne(34)
#define GATEWAY 2454 //debut memoire(2048)+ 2* 3eme ligne(192)+2*offset dansligne(11)=2454
#define ENEMYINHOME 1
#define ENEMYINGATEWAY 2
#define ENEMYINMAZE 3
#define ENEMYISVULNERABLE 4
#define ENEMYISDEAD 5
//******************************************************************************************************************************
// data type definitions and the corresponding arrays
//**********************************************************************************************************
//**********************************************************************************************************
// functions prototypes
//**********************************************************************************************************
void my_delay(unsigned int delay);
unsigned char PseudoAleat(uint16_t Lim);
void setpacmanXY(uint8_t x,unsigned char y);
void setenemyXY(uint8_t x,unsigned char y);
void putLevel(uint8_t level);
void putScore(uint16_t Score);
unsigned int computeTileAddress(unsigned char x,unsigned char y);
unsigned char computePossibleWays(unsigned char x,unsigned char y);
unsigned char computeDistance(unsigned char x,unsigned char y, unsigned char xe, unsigned char ye);
void eat_Pac_Gomme(unsigned int addr,uint16_t *score,uint8_t level);
void waitStart();
//**********************************************************************************************************
// global variables instead of local to save space in stack
//**********************************************************************************************************
unsigned int score=0,softTimer=0;
unsigned char GhostState; // si cette variable est ajoutée en locale au main : plantage assuré !!!
//**********************************************************************************************************
// main
//**********************************************************************************************************
int main (void)
{
char dx=0,dy=0,dxf=0,dyf=0,*gateway_Tile;
// position pacman : sa position réelle est (x*2,y*2) à cause du matériel qui laisse tomber le poids faible
unsigned char x=8,y=16,distance; // en haut à gauche du labyrinthe
unsigned char level=1, theWay, theWay2, vPORTB=0,xe,ye,aleat=0x0A;
DDRB=0x0F;
PORTB=vPORTB;
xe = 48;ye=40;
GhostState=ENEMYINHOME;
gateway_Tile = (char *) GATEWAY; // pointe sur pavé entrée maison ennemi
// on ferme virtuellement la maison !
*gateway_Tile=0x20;gateway_Tile++;gateway_Tile++;*gateway_Tile=0x20;gateway_Tile = (char *) GATEWAY;
setpacmanXY(x,y);
setenemyXY(xe,ye);
putLevel(level);
putScore(score);
waitStart();
while(1) {
//******* gestion des directions avec mémorisation ************
// joystick et choix du bon pacman
if (bit_is_set(PINB,PINB6)) {dyf = 4; dxf = 0;vPORTB|=0x03;}
if (bit_is_set(PINB,PINB7)) {dyf = -4; dxf = 0;vPORTB=(vPORTB &0xFE)|0x02;}
if (bit_is_set(PINB,PINB1)) {dxf = 2; dyf = 0;vPORTB&=0xFC;}
if (bit_is_set(PINB,PINB0)) {dxf = -2; dyf = 0;vPORTB=(vPORTB&0xFD)|0x01;}
theWay2 = computePossibleWays(xe,ye); // compute enemy's possible way
theWay = computePossibleWays(x,y); // compute pacman's possible way
if (((x & 0x03)==0x00) && ((y & 0x07)==0x00)) { // si au centre d'un pavé
// theWay = computePossibleWays(x,y);
dx=dxf;dy=dyf; // changement de direction seulement quand sur pavé
if (((theWay & 0x08)==0x00) && (dy == -4)) dy=0; // empêche de monter
if (((theWay & 0x04)==0x00) && (dy == 4)) dy=0; // empêche de descendre
if (((theWay & 0x02)==0x00) && (dx == -2)) dx=0; // empêche d'aller à gauche
if (((theWay & 0x01)==0x00) && (dx == 2)) dx=0; // empêche d'aller à droite
eat_Pac_Gomme(computeTileAddress(x,y),&score,level);
} //end if (((x & 0x03)==0x00) && ((y & 0x07)==0x00))
y = y + dy;
x = x + dx;
// gestion des échapatoires latérales
if (x > 126) x = 4;
if(x < 4) x = 128;
// gestion des échapatoires verticales
if (y > 96) {
y = 4; x= 44; // il faut aussi déplacer x !
}
if (y < 4) {
y = 96; x=48; // il faut aussi déplacer x !
}
// Enfin on positionne pacman
setpacmanXY(x,y);
/*
// utilisation du score pour vérifier la detection de Up Down Left Right
score=0;
if (theWay2 & 0x08) score|=0x1000;
if (theWay2 & 0x04) score|=0x0100;
if (theWay2 & 0x02) score|=0x0010;
if (theWay2 & 0x01) score|=0x0001;
// putScore(GhostState);
*/
// managing enemy
switch(GhostState) {
case ENEMYINHOME :
aleat = PseudoAleat(0x007F); //gene pseudoaleat
if ((theWay2 & aleat & 0x08)) ye -= 8; else
if ((theWay2 & aleat & 0x04)) ye += 8; else
if ((theWay2 & aleat & 0x02)) xe -= 4; else
if ((theWay2 & aleat & 0x01)) xe += 4;
// if (xe > 36 && xe < 48)
if(ye > 24 && ye < 32) ye += 8;
if (softTimer>=0x003F) {
GhostState = ENEMYINGATEWAY;
//softTimer = 0;
}
break;
case ENEMYINGATEWAY : // objectif : ye = 16; xe = 44;
if (xe > 44) xe -=2; else
if (xe < 44) xe +=2;
if (xe == 44) { // si destination en x on commence sur y
if (ye > 16) ye -=4; else
if (ye < 16) ye +=4;
}
if ((xe == 44) && (ye == 16)) GhostState = ENEMYINMAZE;
// on ouvre la maison !
// *gateway_Tile=0x00;gateway_Tile++;gateway_Tile++;*gateway_Tile=0x00;gateway_Tile = (char *) GATEWAY;
break;
case ENEMYINMAZE :
// on ferme virtuellement la maison !
// *gateway_Tile=0x20;gateway_Tile++;gateway_Tile++;*gateway_Tile=0x20;gateway_Tile = (char *) GATEWAY;
distance = computeDistance(x,y,xe,ye);
if ((computeDistance(x,y,xe,ye+8) < distance) && (theWay2 & 0x04)) ye += 8; else
if ((computeDistance(x,y,xe,ye-8) < distance) && (theWay2 & 0x08)) ye -= 8; else
if ((computeDistance(x,y,xe+4,ye) < distance) && (theWay2 & 0x01)) xe += 4; else
if ((computeDistance(x,y,xe-4,ye) < distance) && (theWay2 & 0x02)) xe -= 4; else {
// ici si enemy a attrappé pacman ou pas de possibilité de mouvement
// pacman catched ?
if ((xe==x) && (ye==y)) { // if enemy catchs pacman
GhostState = ENEMYINHOME;
xe = 48; ye = 40;
x = 8; y = 16;
setpacmanXY(x,y);
setenemyXY(xe,ye);
// BCD level management
level++;
// conversion BCD
if ((level &0x0F) > 9) level = level+6;
if ((level &0xF0) > 0x90) level = level+0x60;
putLevel(level);
//xe=44;ye=16;
softTimer=0;
//my_delay(2000);
waitStart();
} else { // probleme si l’on vient ici, on a toutes les chances de boucler => aleat
aleat = PseudoAleat(0x007F); //gene pseudoaleat
if ((theWay2 & aleat & 0x04)) ye += 8; else
if ((theWay2 & aleat & 0x08)) ye -= 8; else
if ((theWay2 & aleat & 0x02)) xe -= 4; else
if ((theWay2 & aleat & 0x01)) xe += 4;
}
}
break;
default : ye = 16; xe = 44;
} // switch
// Enemy never goes out of maze
if (ye < 16 ) ye = 16;
if (ye > 80 ) ye = 80;
if (xe < 8 ) xe = 8;
if (xe > 124 ) xe = 124;
setenemyXY(xe,ye);
vPORTB = vPORTB ^0x04;//toggle : basculement ouvert/fermé du pacman
PORTB = vPORTB;
my_delay(200);
softTimer++;
} //end while(1)
} //end main
//**********************************************************************************************************
// 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;
}
//**********************************************************************************************************
// 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;
}
//**********************************************************************************************************
// function PseudoAleat()
// purpose: simple random generator
// arguments:
// corresponding modulo = nb different values :12 for us
// return:
// note:
//**********************************************************************************************************
unsigned char PseudoAleat(uint16_t Lim){
static uint16_t Y = 1;
Y = (Y * 32719 + 3) % 32749;
return (Y % Lim); // aucun offset
// ci-dessous ne fonctionne pas
// static uint8_t Y = 1;
// Y++;
// return Y;
}
//**********************************************************************************************************
// function my_delay()
// purpose: time consuming function to waste time
// arguments:
// corresponding delay
// return:
// note:
//**********************************************************************************************************
void my_delay(unsigned int delay){
int i;
for(i=0;i<delay;i++) _delay_ms(1);
}
//**********************************************************************************************************
// 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 *afflevel;
afflevel = (unsigned char *)LEVELUNIT; //debut memoire+derniere ligne+offset dans ligne +25???
*afflevel = (level &0x0F)+'0';
afflevel--;afflevel--;
*afflevel = ((level &0xF0)>>4)+'0';
}
//**********************************************************************************************************
// function putSore()
// purpose: put score in the screen by writing in RAM (RAMB16_S9_S9)
// arguments:
// corresponding Score
// return:
// note: only four digits are managed at the moment
//**********************************************************************************************************
void putScore(uint16_t Score){
unsigned char *affscore;
affscore = (unsigned char *) SCOREUNIT;//debut memoire+derniere ligne+offset dans ligne +25???
*affscore = (Score &0x000F)+'0';
affscore--;affscore--;
*affscore = ((Score &0x00F0)>>4)+'0';
affscore--;affscore--;
*affscore = ((Score &0x0F00)>>8)+'0';
affscore--;affscore--;
*affscore = ((Score &0xF000)>>12)+'0';
}
//**********************************************************************************************************
// 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; // coordonnées du centre du sprite 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;
}
//**********************************************************************************************************
// function computePossibleWays()
// purpose: compute the possible ways 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 : if set the corresponding way is free
// 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
// ajouté l'espace (0x20) pour fermeture invisible a l'ecran 15/01/13
if ((*ramB16 != 0x07)&&(*ramB16 != 0x20)) res=res|0x08; //si pas brique on peut y aller
ramB16 = (unsigned char *)(addr + 128); // 64 en principe mais mémoire organisée ainsi : down
if ((*ramB16 != 0x07)&&(*ramB16 != 0x20)) res=res|0x04; //si pas brique on peut y aller
ramB16 = (unsigned char *)(addr - 2); // 1 en principe mais mémoire organisée ainsi : left
if ((*ramB16 != 0x07)&&(*ramB16 != 0x20)) res=res|0x02; //si pas brique on peut y aller
ramB16 = (unsigned char *)(addr + 2); // 1 en principe mais mémoire organisée ainsi : right
if ((*ramB16 != 0x07)&&(*ramB16 != 0x20)) res=res|0x01; //si pas brique on peut y aller
return res;
}
//**********************************************************************************************************
// function computeDistance()
// purpose: compute the distance between enemy and pacman
// arguments:
// corresponding x and y coordinates and xe and ye
// return: a non-euclien distance
// note:
//**********************************************************************************************************
unsigned char computeDistance(unsigned char x,unsigned char y, unsigned char xe, unsigned char ye){
unsigned char res;
char delta;
delta = xe-x; if (delta <0) res = -delta; else res = delta;
delta = ye-y; if (delta <0) res -= delta; else res += delta;
return res;
}
//**********************************************************************************************************
// function eat_Pac_Gomme()
// purpose: eat a pac gomme if it exixt
// arguments:
// corresponding address, corresponding score, corresponding level
// return: new score is updated
// note: you cannot understand if you never read the corresponding VHDL code.
//**********************************************************************************************************
void eat_Pac_Gomme(unsigned int addr,uint16_t *score,uint8_t level){
unsigned char *ramB16;
ramB16 = (unsigned char *) (addr);
if (*ramB16 == 0x04) {
*ramB16 = 0x00; //mangé et digéré
(*score)++;
if (level == 1)
(*score)++;
// BCD score management
// conversion BCD
if (((*score) &0x000F) > 9) (*score) = (*score)+6;
if (((*score) &0x00F0) > 0x0090) (*score) = (*score)+0x0060;
if (((*score) &0x0F00) > 0x0900) (*score) = (*score)+0x0600;
if (((*score) &0xF000) > 0x9000) (*score) = (*score)+0x6000;
putScore((*score));
}
}
//**********************************************************************************************************
// 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(){
// les boutons sont inversés dans la Nunchuk
// Z button in 2
// je n'ai pas réussi à faire marcher les boutons !!!!
while (!bit_is_set(PINB,PINB1)) ; // un coup sur droite pour débloquer
}
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// ----------------------------------------------------------
// Projet TR Geii 2013 : PACMAN
// Réalisé par : Guillaume Demarquez, Pauline Skorupka
// ----------------------------------------------------------
#include <avr/io.h>
#undef F_CPU
#define F_CPU 25000000UL
#include "util/delay.h"
/* Port A */
//#define PINA _SFR_IO8(0x19)
// Les ports ci-dessous n'existent pas dans l'ATMega8 de la vraie vie mais nous les avons ajouté dans notre cœur
// d'où leur présence ici : ne pas modifier les fichiers d'entête !
#define DDRA _SFR_IO8(0x1A)
#define PORTA _SFR_IO8(0x1B)
#define LEVELUNIT 1958+1024+34+900 //debut memoire(2048)+2*derniere ligne(900)+2*offset dans ligne(34)
void putLevel(uint8_t level);
void setpacmanXY(uint8_t x,unsigned char y);
void setfantomeXY(uint8_t x,unsigned char y);
unsigned char computePossibleWays(unsigned char x,unsigned char y);
unsigned int computeTileAddress(unsigned char x,unsigned char y);
void My_delay(unsigned int delay);
void putScore(uint16_t score);
void eatPacGomme(unsigned char x, unsigned char y, uint16_t *score, unsigned char *cycle, unsigned char *nb_gomme);
void pathFinding(uint8_t pacmanY, uint8_t pacmanX, uint8_t fantomeY, uint8_t fantomeX, unsigned char *vPORTB);
void moveFantome(uint8_t nb_move, char *fdx, char *fdy);
void movePacman(uint8_t pacmanX, uint8_t pacmanY, char *dx, char *dy, unsigned char *vPORTB, char *move);
void increaseScore(uint16_t *score, uint8_t nb_points);
void putMessage(uint8_t message);
//on déclare un tableau 2D de la taille du jeu (en char car nombres petits)
//11(nb lignes) et 32(nb colonnes) prennent en compte les murs autour !
int8_t map[11][32]={{0},{0}};
//tableau contenant le chemin à parcourir (taille prévue large pour l'instant)
unsigned char road_map[80]={
0x00,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
0x08,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x04,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
0x08,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x04,0x02,0x02,0x02,0x02,0x02,
}; //on met dedans le chemin initial du pacman
int main (void)
{
uint8_t level=2;
uint16_t score=0;
uint8_t pacmanY=16,pacmanX=8,fantomeY=40,fantomeX=68;
//pacmanY=8 et pacmanX=4 dans la case 00 de la map (en haut à gauche)
//déplacement de 1 à droite = +4
//déplacement de 1 en bas = +8
//char pacmanY=1,pacmanX=1,fantomeY=4,fantomeX=17; //valeurs correspondant aux case du tableaux
char dy=0,dx=0,fdx=0,fdy=0;
uint8_t nb_move=0;
char pacman_speed=1,fantome_speed=1;
unsigned char vPORTB=0;
char move=0;
unsigned char cycle=0;//0:cycle init, 1:cycle normal, 2:cycle fruit, 3:fin du jeu ?
int nb_move_pacman=0;
unsigned char nb_gomme=0;
DDRB=0x0F;
PORTB=0;
//on met des murs tout autour de la map pour ne pas avoir de problèmes pour la suite
uint8_t i=0;
for(i=0;i<11;i++){
map[i][0]=-1;
map[i][31]=-1;
}
for(i=0;i<32;i++){
map[0][i]=-1;
map[10][i]=-1;
}
unsigned char *adr_message;
//on se place au bout à droite et on remonte de n lignes (n=2 ici)
adr_message = (unsigned char *)LEVELUNIT; //debut memoire+derniere ligne+offset dans ligne
//si le code message est 1, on affiche GAME OVER
*adr_message = level + '0';
adr_message-=10;
*adr_message = 'S';
adr_message-=2;
*adr_message = 'E';
adr_message-=2;
*adr_message = 'V';
adr_message-=2;
*adr_message = 'I';
adr_message-=2;
*adr_message = 'L';
//on place le pacman et la fantome sur le terrain
setpacmanXY(pacmanX,pacmanY);
setfantomeXY(fantomeX,fantomeY);
while(1){
My_delay(200);
putLevel(level);
putScore(score);
setpacmanXY(pacmanX,pacmanY);
setfantomeXY(fantomeX,fantomeY);
//level++;
//gestion du pacman
if((dx!=0)||(dy!=0)){
//on inverse le sprite du pacman (ouvert<-->fermé)
vPORTB^=0x04;
}
if(((pacmanX&0x03)==0)&&((pacmanY&0x07)==0)){
//on calcul les dx,dy correspondant au mouvement
eatPacGomme(pacmanX,pacmanY,&score,&cycle,&nb_gomme);
movePacman(pacmanX,pacmanY,&dx,&dy,&vPORTB,&move);
}
//gestion du fantome
//cycle initial : le fantome tourne dans sa cage
if(cycle==0){
//on fait tourner le pacman en boucle dans sa cage
if(nb_move==42){//si on a fait tous les mouvements précalculés
//on donne au fantome sa vitesse normale
//on passe en cycle normal
cycle=1;
nb_move=0; //pas forcément utile : À VERIFIER
}
//sinon, on bouge le fantome selon ce qu’il y a dans le tableau
else if(((fantomeX&0x03)==0)&&((fantomeY&0x07)==0)){
moveFantome(nb_move,&fdx,&fdy);
nb_move++;
}
}
//cycle normal : le fantome suit le pacman à la trace
else if(cycle==1){
//cycle 1 simplifié
if(((fantomeX&0x03)==0)&&((fantomeY&0x07)==0)){
//si on est sur une case, on calcul le chemin à suivre
pathFinding(pacmanY,pacmanX,fantomeY,fantomeX,&vPORTB);
//on repart de la case 0 du road_map et on bouge le fantome
nb_move=0;
/*if((pacmanX>7)&&(pacmanX<125)&&(pacmanY>15)&&(pacmanY<81)){
moveFantome(nb_move,&fdx,&fdy);
}*/
//si le pacman est dans les échappatoires, on ne bouge pas le fantome
if((pacmanX<8)&&(pacmanX>124)&&(pacmanY<16)&&(pacmanY>80)){
moveFantome(99,&fdx,&fdy);
}
else moveFantome(nb_move,&fdx,&fdy);
}
}
//cycle de fuite : le fantome tente de s'échapper
else if(cycle==2){
pacman_speed=2;
if(((fantomeX&0x03)==0)&&((fantomeY&0x07)==0)){
if(pacmanX<68){
if(pacmanY<48){ //5ème case du tableau
//on envoie le fantome en bas à droite
pathFinding(80,124,fantomeY,fantomeX,&vPORTB);
}
else{
//on envoie le fantome en haut à droite
pathFinding(16,124,fantomeY,fantomeX,&vPORTB);
}
}
else if(pacmanY<48){
//on envoie le fantome en bas à gauche
pathFinding(80,8,fantomeY,fantomeX,&vPORTB);
}
else{
//on envoie le fantome en haut à gauche
pathFinding(16,8,fantomeY,fantomeX,&vPORTB);
}
nb_move=0;
moveFantome(nb_move,&fdx,&fdy);
nb_move_pacman++;
//au bout d'un certain temps, on retourne en cycle 1
if(nb_move_pacman>60){
cycle=1;
pacman_speed=1;
}
}
}
//cycle de retour en étape initiale : le fantome vient de se faire manger
else if(cycle==3){
fantome_speed=1;
if((fantomeX==64)&&(fantomeY==40)){
//retour cycle 1
vPORTB=vPORTB&0xF7;
cycle=1;
putMessage(0);
}
else if(((fantomeX&0x03)==0)&&((fantomeY&0x07)==0)){
pathFinding(40,64,fantomeY,fantomeX,&vPORTB);
nb_move=0;
moveFantome(nb_move,&fdx,&fdy);
vPORTB=vPORTB|0x08;
}
}
//on déplace le fantome selon le déplacement du tableau
fantomeX+=(fdx*fantome_speed);
fantomeY+=(fdy*fantome_speed);
//gestion des échapatoires latérales
if(pacmanX>128) pacmanX=4;
if(pacmanX<4) pacmanX=128;
//gestion des échapatoires verticales
if(pacmanY>96){
pacmanY=4;
pacmanX=44;
}
if(pacmanY<4){
pacmanY=96;
pacmanX=48;
}
//on déplace le pacman selon le déplacement choisi
pacmanX+=(dx*pacman_speed);
pacmanY+=(dy*pacman_speed);
//conversion décimal -> BCD des affichages score et level
if((level &0x0F)>9) level+=6;
if((level &0xF0)>144) level=0;
//on regarde si le pacman et le fantome se marchent dessus
if((pacmanX-fantomeX>-3)&&(pacmanX-fantomeX<3)&&(pacmanY-fantomeY>-6)&&(pacmanY-fantomeY<6)){
//si on est dans le cycle 1, le fantome mange le pacman --> perdu
if(cycle==1){
level-=1;
if(level==0){
//on affiche GAME OVER
putLevel(0);
putMessage(1);
while(1);
}
pacmanX=8;
pacmanY=16;
}
//si on est dans le cycle 2, le pacman mange le fantome --> le jeu continu
else if(cycle==2){
//retour dans le cycle 1 via le cycle 3
cycle=3;
putMessage(3);
pacman_speed=1;
level+=1;
increaseScore(&score,200);
}
}
//si on a mangé toutes les gommes, on a gagné :
if(nb_gomme>120){
putMessage(2);
while(1);
}
//on met à jour les sprites
PORTB=vPORTB;
}
return 0;
}
//******************************************************************************************************************************
// 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 *afflevel;
afflevel = (unsigned char *)LEVELUNIT; //debut memoire+derniere ligne+offset dans ligne
*afflevel = (level &0x0F)+'0';
afflevel--;afflevel--;
*afflevel = ((level &0xF0)>>4)+'0';
}
//******************************************************************************************************************************
// function putScore()
// purpose: put score in the screen by writing in RAM (RAMB16_S9_S9)
// arguments:
// corresponding score
// return:
// note:
//******************************************************************************************************************************
void putScore(uint16_t score){
unsigned char *affscore;
affscore = (unsigned char *)LEVELUNIT -26; //debut memoire+derniere ligne+offset dans ligne
*affscore = (score &0x000F)+'0'; //on ajoute le code ASCII de "0"
affscore--;affscore--;
*affscore = ((score &0x00F0)>>4)+'0';
affscore--;affscore--;
*affscore = ((score &0x0F00)>>8)+'0';
affscore--;affscore--;
*affscore = ((score &0xF000)>>12)+'0';
}
//******************************************************************************************************************************
// 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;
}
//******************************************************************************************************************************
// 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
ramB16 = (unsigned char *)(addr + 128); // 64 en principe mais mémoire organisée ainsi : up
if (*ramB16 != 0x07) res=res|0x04; //si pas brique on peut y aller
ramB16 = (unsigned char *)(addr - 2); // -1 en principe mais mémoire organisée ainsi : up
if (*ramB16 != 0x07) res=res|0x02; //si pas brique on peut y aller
ramB16 = (unsigned char *)(addr + 2); // +1 en principe mais mémoire organisée ainsi : up
if (*ramB16 != 0x07) res=res|0x01; //si pas brique on peut y aller
return res;
};
//******************************************************************************************************************************
// 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;
}
//******************************************************************************************************************************
// function My_delay()
// purpose: time consuming function to waste time
// arguments:
// corresponding delay
// return:
// note:
//******************************************************************************************************************************
void My_delay(unsigned int delay){
int i;
for(i=0;i<delay;i++) _delay_ms(1);
}
//******************************************************************************************************************************
// function eatPacGomme()
// purpose:
// arguments:
// return:
// note:
//******************************************************************************************************************************
void eatPacGomme(unsigned char x, unsigned char y, uint16_t *score, unsigned char *cycle, unsigned char *nb_gomme){
char* ptr;
ptr = (char *)computeTileAddress(x,y);
//si on mange une gomme on gagne 1 point
if(*ptr==4){
*ptr=0;
increaseScore(score,10);
*nb_gomme+=1;
}
//si on mange le fruit on gagne 100 points et on passe en cycle 2
else if(*ptr==5){
*ptr=0;
increaseScore(score,100);
*cycle=2;
*nb_gomme+=1;
}
}
//fonction calculant le score équivalent en hexadécimal
//cela permet de modifier le score d'une valeur quelconque sans faire de conversions préalable
void increaseScore(uint16_t *score, uint8_t nb_points){
uint8_t i=0;
for(i=0;i<nb_points;i++){
*score+=1; // (*score)++;
if((*score&0x000F)>9) *score+=0x0006;
if(((*score&0x00F0)>>4)>9) *score+=0x0060;
if(((*score&0x0F00)>>8)>9) *score+=0x0600;
if(((*score&0xF000)>>12)>9) *score+=0x6000;
if(*score>39321) *score=0;
}
}
//******************************************************************************************************************************
// function setfantomeXY()
// purpose:
// arguments:
// return:
// note:
//******************************************************************************************************************************
void setfantomeXY(uint8_t x,unsigned char y){
DDRA=x;
DDRC=y;
}
//**********************************************************
//pathFinding
//met dans le tableau une valeur correspondant à sa proximité avec la case précédente
//on se déplace selon une distance de Manhattan
//arguments : pacmanX, pacmanY, etc..
//**********************************************************
void pathFinding(uint8_t pacmanY, uint8_t pacmanX, uint8_t fantomeY, uint8_t fantomeX, unsigned char *vPORTB){
unsigned char i=0,j=0;
unsigned char cout=1,nb_mouvements=0;
unsigned char the_way=0; //à transformer en pointeur pour moins de mémoire ?
//on transforme les position x,y en case du tableau
pacmanY=(pacmanY-8)>>3; //pacmanY=(pacmanY-8)/8;
pacmanX=(pacmanX-4)>>2; //pacmanX=(pacmanX-4)/4;
fantomeY=(fantomeY-8)>>3;
fantomeX=(fantomeX-4)>>2;
//on commence par reset le tableau en gardant seulement les murs
for(i=1;i<31;i++){
for(j=1;j<10;j++){
if(map[j][i]!=-1) map[j][i]=0; //si pas de mur, on met la case à 0 (trajet possible)
}
}
//on reset aussi le road_map
for(i=0;i<80;i++){
road_map[i]=0;
}
//on marque la position du pacman avec un cout de 1
map[pacmanY][pacmanX]=1;
//on continue les opérations tant qu'on a pas atteint la case du pacman
while(map[fantomeY][fantomeX]==0){
for(i=1;i<31;i++){
for(j=1;j<10;j++){
//si la case a le cout actuel (le plus élevé) on donne un cout aux cases autour
if(map[j][i]==cout){
//on test les déplacements possible à partir de la case en cours
//the_way=computePossibleWays(((i*4)+4),((j*8)+8));
the_way=computePossibleWays(((i<<2)+4),((j<<3)+8)); //on test les possibilités quand on est dans la case (i,j)
//si la case n'a pas de cout on met cout+1 sinon on indique un mur (-1)
if(map[j][i+1]==0){ //case à droite
if((the_way&0x01)==0x01) map[j][i+1]=cout+1;
else map[j][i+1]=-1;
}
if(map[j][i-1]==0){ //case à gauche
if((the_way&0x02)==0x02) map[j][i-1]=cout+1;
else map[j][i-1]=-1;
}
if(map[j-1][i]==0){ //case en haut
if((the_way&0x08)==0x08) map[j-1][i]=cout+1;
else map[j-1][i]=-1;
}
if(map[j+1][i]==0){ //case en bas
if((the_way&0x04)==0x04) map[j+1][i]=cout+1;
else map[j+1][i]=-1;
}
}
}
}
//on incrémente le cout pour les cases plus loin
cout++;
}
//le chemin jusqu'au pacman est maintenant mappé, il faut donc récupérer les mouvements à effectuer
//on part de la case du pacman
i=fantomeX;
j=fantomeY;
while(cout>1){
//si le cout de la case autour = cout-1
//dans road_map on met le mouvement inverse car on fera le trajet dans l'autre sens
if(map[j+1][i]==(cout-1)){
road_map[nb_mouvements]=0x04; //on va en bas
j+=1; //on se met dans la case suivante
}
else if(map[j-1][i]==(cout-1)){
road_map[nb_mouvements]=0x08; //on va en haut
j-=1;
}
else if(map[j][i+1]==(cout-1)){
road_map[nb_mouvements]=0x01; //on va à droite
i+=1;
}
else if(map[j][i-1]==(cout-1)){
road_map[nb_mouvements]=0x02; //on va à gauche
i-=1;
}
nb_mouvements++;
cout--;
}
}
//**********************************************************
//moveFantome
//à partir de la valeur du tableau, on calcul les dx,dy du fantome
//**********************************************************
void moveFantome(uint8_t nb_move, char *fdx, char *fdy){
//si on envoi le code 99 à la fonction, on stop le fantome sur place
if (nb_move==99){
*fdy=0;
*fdx=0;
}
else if(road_map[nb_move]==0x08){ //haut
*fdx=0;
*fdy=-2;
}
else if(road_map[nb_move]==0x04){ //bas
*fdx=0;
*fdy=2;
}
else if(road_map[nb_move]==0x02){ //gauche
*fdy=0;
*fdx=-1;
}
else if(road_map[nb_move]==0x01){ //droite
*fdy=0;
*fdx=1;
}
else{ //sinon, on ne bouge pas
*fdy=0;
*fdx=0;
}
}
//****************************************************************
//movePacman
//cette fonction sert à gérer le déplacement du pacman
//on appelle cette fonction que lorsque l’on est sur une case
//on peut déclarer vPORTB en variable globale au pire
// ou le laisser en pointeur...
//****************************************************************
void movePacman(uint8_t pacmanX, uint8_t pacmanY, char *dx, char *dy, unsigned char *vPORTB, char *move){
unsigned char the_way=0;
unsigned char old_move=0;
the_way=computePossibleWays(pacmanX,pacmanY);
//on garde en mémoire l'ancien déplacement
old_move=*move;
if(bit_is_set(PINB,PINB7)) *move=1; //haut
else if(bit_is_set(PINB,PINB6)) *move=2; //bas
else if(bit_is_set(PINB,PINB0)) *move=3; //gauche
else if(bit_is_set(PINB,PINB1)) *move=4; //droite
//si on a rien demandé, on reprend le mouvement précédent
else *move=old_move;
//déplacement vers le haut
if((*move==1)&&((the_way&0x08)==0x08)){
*dx=0;
*dy=-2;
*vPORTB=(*vPORTB&0xFE)|0x02;
}
//déplacment vers le bas
else if((*move==2)&&((the_way&0x04)==0x04)){
*dx=0;
*dy=2;
*vPORTB|=0x03;
}
//déplacement vers la gauche
else if((*move==3)&&((the_way&0x02)==0x02)){
*dx=-1;
*dy=0;
*vPORTB=(*vPORTB&0xFD)|0x01;
}
//déplacement vers la droite
else if((*move==4)&&((the_way&0x01)==0x01)){
*dx=1;
*dy=0;
*vPORTB&=0xFC;
}
else{ //déplacement impossible : on ne bouge plus
*dx=0;
*dy=0;
*move=0;
}
}
//message perso
void putMessage(uint8_t message){
unsigned char *adr_message;
//on se place au bout à droite et on remonte de n lignes (n=2 ici)
adr_message = (unsigned char *)LEVELUNIT - (128); //debut memoire+derniere ligne+offset dans ligne
//si le code message est 1, on affiche GAME OVER
if(message==1){
*adr_message = 'R';
adr_message-=2;
*adr_message = 'E';
adr_message-=2;
*adr_message = 'V';
adr_message-=2;
*adr_message = 'O';
adr_message-=2;
*adr_message = 0;
adr_message-=2;
*adr_message = 'E';
adr_message-=2;
*adr_message = 'M';
adr_message-=2;
*adr_message = 'A';
adr_message-=2;
*adr_message = 'G';
}
//message 2 : YOU WIN
else if(message==2){
*adr_message = 'N';
adr_message-=2;
*adr_message = 'I';
adr_message-=2;
*adr_message = 'W';
adr_message-=2;
*adr_message = 0;
adr_message-=2;
*adr_message = 'U';
adr_message-=2;
*adr_message = 'O';
adr_message-=2;
*adr_message = 'Y';
adr_message-=2;
*adr_message = 0;
adr_message-=2;
*adr_message = 0;
}
//message 0 : on clean l'affichage
else if(message==0){
*adr_message = 0;
adr_message-=2;
*adr_message = 0;
adr_message-=2;
*adr_message = 0;
adr_message-=2;
*adr_message = 0;
adr_message-=2;
*adr_message = 0;
adr_message-=2;
*adr_message = 0;
adr_message-=2;
*adr_message = 0;
adr_message-=2;
*adr_message = 0;
adr_message-=2;
*adr_message = 0;
}
//message 3 : NICE ONE
else if(message==3){
*adr_message = 'E';
adr_message-=2;
*adr_message = 'N';
adr_message-=2;
*adr_message = 'O';
adr_message-=2;
*adr_message = 0;
adr_message-=2;
*adr_message = 'E';
adr_message-=2;
*adr_message = 'C';
adr_message-=2;
*adr_message = 'I';
adr_message-=2;
*adr_message = 'N';
adr_message-=2;
*adr_message = 0;
}
}
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
modifierNotez 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
modifierLe 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 :
NET I_CLK_50 PERIOD = 20 ns; NET I_CLK_50 TNM_NET = I_CLK_500; NET I_CLK_50 LOC = "T9"; #liaison série #net "serial_out" loc = "r13"; #net "serial_in" loc = "t13"; NET I_RX LOC = T13; NET Q_TX LOC = R13; # 7 segment LED display : PORTB NET Q_7_SEGMENT<6> LOC = "N16"; NET Q_7_SEGMENT<5> LOC = "F13"; NET Q_7_SEGMENT<4> LOC = "r16"; NET Q_7_SEGMENT<3> LOC = "P15"; NET Q_7_SEGMENT<2> LOC = "N15"; NET Q_7_SEGMENT<1> LOC = "g13"; NET Q_7_SEGMENT<0> LOC = "e14"; # sélection afficheur : PORTD net "Q_AN<0>" loc="d14"; net "Q_AN<1>" loc="g14"; net "Q_AN<2>" loc ="f14"; net "Q_AN<3>" loc ="e13"; # single LEDs : PORTC NET Q_LEDS<0> LOC = "K12"; NET Q_LEDS<1> LOC = "P14"; NET Q_LEDS<2> LOC = "L12"; NET Q_LEDS<3> LOC = "N14"; NET Q_LEDS<4> LOC = "P13"; NET Q_LEDS<5> LOC = "N12"; NET Q_LEDS<6> LOC = "P12"; NET Q_LEDS<7> LOC = "P11"; # DIP switch(0 ... 7) # #NET I_SWITCH<0> LOC = "f12"; #NET I_SWITCH<1> LOC = "g12"; NET I_SWITCH<2> LOC = "h14"; NET I_SWITCH<3> LOC = "h13"; NET I_SWITCH<4> LOC = "j14"; NET I_SWITCH<5> LOC = "j13"; #NET I_SWITCH<6> LOC = "k14"; #NET I_SWITCH<7> LOC = "k13"; #deplacer switch pour Joystick NET I_SWITCH<0> LOC = "E7"; NET I_SWITCH<1> LOC = "D7"; NET I_SWITCH<6> LOC = "D5"; NET I_SWITCH<7> LOC = "D6"; #NET I_SWITCH<*> PULLUP; #one pushbuttons (3) NET I_CLR LOC = "L14"; #NET I_Reset<1> LOC = "L14"; #VGA net "hsynch" loc="R9"; net "vsynch" loc="T10"; net "red" loc="R12"; net "blue" loc="R11"; net "green" loc="T12"; INST cpu/opcf/pmem/pe_1 LOC = RAMB16_X1Y2; INST cpu/opcf/pmem/pe_0 LOC = RAMB16_X0Y3; INST cpu/opcf/pmem/pe_3 LOC = RAMB16_X1Y3; INST cpu/opcf/pmem/pe_2 LOC = RAMB16_X0Y2; INST cpu/opcf/pmem/po_1 LOC = RAMB16_X1Y0; INST cpu/opcf/pmem/po_0 LOC = RAMB16_X0Y0; INST cpu/opcf/pmem/po_3 LOC = RAMB16_X1Y1; INST cpu/opcf/pmem/po_2 LOC = RAMB16_X0Y1;
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