Very High Speed Integrated Circuit Hardware Description Language/Le NIOS d'Altera
Comme l'indique le titre de ce chapitre, nous allons utiliser le processeur N.I.O.S. de chez Altera. Il s'agit d'un processeur 32 bits de type RISC propriétaire, c'est-à-dire que vous ne disposez pas de son code source.
Les cartes que l'on utilise avec les étudiants de 2ème année étant des DE2-115, l'essentiel de ce qui va être décrit ici sera destiné à ces cartes. Il n'y a aucun problème pour l'adapter à d'autres cartes. Seul le fichier de contraintes sur les broches de FPGA est différent.
Au risque de nous répéter, nous avons beaucoup utilisé de processeurs libres jusqu'à présent. Mais les petits problèmes rencontrés nous incitent à utiliser des processeurs réalisés par une équipe d'ingénieurs. Parmi les problèmes rencontrés se trouve la difficulté que nous avons à porter sur Altera, notre processeur préféré jusqu'à présent décrit dans un chapitre de ce livre sur l'ATMega8 d'une part, et son amélioration dans le chapitre suivant.
Ils sont tout simplement plus fiables mais en général sont propriétaires. L'environnement de développement lui aussi est propriétaire, mais vous pouvez le télécharger et avoir une licence WEBPACK gratuite. Tous les outils utilisés dans ce chapitre sont gratuits.
Introduction
[modifier | modifier le wikicode]Malgré l'expérience accumulée par l'écriture de ce livre, il nous a fallu une dizaine d'heure pour faire tourner notre premier NIOS. Ceci est très anecdotique mais vous montre quand même que dans le domaine des SOCs rien n'est jamais simple. Vous pouvez trouver des tutoriels sur Youtube, des documents PDF sur internet. La documentation ne manque pas, mais vous êtes arrêté par de simples détails, parce que ce que vous lisez ou voyez dans ces documents, ne correspond pas ce que vous avez sous les yeux dans votre environnement de développement matériel. Nous ne pourrons malheureusement pas faire mieux que les autres car nous ignorons totalement quelle version de Quartus vous utilisez au moment où vous lisez ces lignes. La seule chose que nous pouvons préciser est que nous utilisons la version 15.0 de Quartus qui n'est déjà plus la dernière à ce jour.
Nous allons essayer d'expliquer comment les choses sont liées plutôt que de donner des copies d'écran qui seront obsolètes très vite. Si vous n'êtes pas arrivé directement ici et avez lu quelques chapitres de ce livre, vous savez que le problème des SOCs est de relier la chaîne de compilation matérielle et la chaîne de compilation logicielle.
Mon premier exemple
[modifier | modifier le wikicode]La réalisation d'un SOC se fait en deux grandes étapes :
- la réalisation matérielle
- la programmation du matériel par du logiciel
Nous allons organiser la suite en respectant ces étapes, d'autant plus qu'elles utilisent des logiciels complètement différent.
Réalisation matérielle
[modifier | modifier le wikicode]La réalisation matérielle utilise deux logiciels complètement différent :
- Quartus pour la synthèse finale
- QSys (ancien SOPC) pour la définition du NIOS (deveindra Platform Designer)
C'est l'utilisation de deux logiciels différents qui rend les choses délicates pour une première utilisation. L'objectif de QSys est de générer un fichier d'extension .qip dans lequel se trouve un fichier VHDL (ce n'est pas VHDL par défaut mais verilog). Ce fichier doit avoir 1 seule entrée, l'horloge et 8 sorties. Cela correspond à notre cahier des charges : les chenillars étant courant dans les tutoriels trouvés sur Internet, nous allons nous intéresser à un compteur qui sortira sa valeur sur un afficheur sept segments.
Quartus
[modifier | modifier le wikicode]On commence par lancer Quartus et par réaliser un projet Quartus. Ce projet s'appellera pour nous niosii, et par défaut nécessitera donc une entité niosii, celle la même qui sera générée automatiquement par QSys. A part positionner le FPGA cible, on n'a donc pas grand chose à faire à ce stade. Basculons donc dans QSys où le gros du travail est à faire.
QSys
[modifier | modifier le wikicode]Quand vous lancez QSys, vous devez voir apparaître un schéma qui laisse penser qu'on va utiliser un module spécifique pour l'horloge. La suite c'est à vous de la composer. L'ordre dans lequel on fait les choses a son importance. A ce stade la plupart d'entre-vous seront tentés de commencer par l'élément le plus important : le processeur. Il n'empêche que vous éviterez un aller et retour dans QSys en commençant par la mémoire.
Mémoire
[modifier | modifier le wikicode]Nous allons prendre une mémoire unique qui servira de mémoire programme et de mémoire données. Les programmes destinés à un processeur 32 bits étant en général plus gros que ceux que l'on a utilisé jusqu'à présent nous allons choisir une mémoire de 32ko.
Pour se faire le travail suivant est nécessaire :
À partir de maintenant la description partielle des menus et sous menu se fera à l'aide de décalages. Par exemple :
- Basic Functions
- onchip Memory
- on chip Memory (RAM or ROM)
- Size 32768
- on chip Memory (RAM or ROM)
- onchip Memory
signifie que vous allez dans Basic function, puis dans on chip Memory puis ....
Nous utiliserons toujours cette convention dans la suite.
Il n'y a que deux adresses possibles pour celle-ci : 0x0 ou 0x8000. Pour une taille de 32 ko nous choisissons 0x8000.
Nous garderons le nom d'instance par défaut : onchip_memory2_0.
Processeur
[modifier | modifier le wikicode]Nous allons choisir le processeur NIOS le plus basique. Il s'appelle NIOS II/e.
- Processors and Peripherals
- Nios ii (classic) Processor
- nios II/e
- Positionner Reset vector Memory dans la mémoire onchip_memory2_0
- Positionner Exception vector Memory dans la mémoire onchip_memory2_0
- nios II/e
- Nios ii (classic) Processor
Périphérique
[modifier | modifier le wikicode]Au vu de notre cahier des charges, un seul port de 8 bits suffit.
- Processors and Peripherals
- Peripheral
- PIO (Parallel I/O)
- 8 bits OUTPUT
- PIO (Parallel I/O)
- Peripheral
Câbler et donner des adresses et exporter les sorties
[modifier | modifier le wikicode]A ce stade, il ne reste plus qu'à câbler l'ensemble et à fixer les adresses de la mémoire (à 0x8000) et du PORT (à 0x1000). Pour ce PORT il faut positionner un nom dans external connexion. Nous avons choisi "s7segs". Cet export est lié au fait que nous voulons sortir directement les informations du PORT sur notre afficheur sept segments.
Comme toujours un schéma comporte des conventions. Il nous a fallu un peu de temps pour nous familiariser avec la partie gauche de cette figure. Ceci fait vous vous apercevrez que les liaisons sont toute à fait normales :
- il est logique d'avoir une liaison entre data_master et l'entrée s1 de la RAM ainsi qu'entre instruction_master et cette même entrée. Ceci est lié à l'utilisation de la RAM en mémoire programme ainsi qu'en RAM du processeur. Autrement dit des données comme des instructions sont susceptibles de transiter entre cette RAM et le processeur NIOSII.
- il est logique d'avoir une liaison entre data_master et l'entrée s1 du PIO. Ce qui nous surprend c'est la possibilité de relier aussi instruction_master à l'entrée s1 de ce même PIO. Nous ne l'avons pas fait car nous ne voyons vraiment pas à quoi cela peut servir.
- Les resets sont reliés ensembles.
Il n'y a plus qu'à exporter l'ensemble de ce projet matériel :
En bas à droite, cliquer sur GENERATE HDL en prenant soin choisir VHDL (qui n'est pas le langage par défaut).
Donner comme nom au fichier .sopc le même nom que votre projet Quartus (pour nous niosii.qsys). Ceci n'est pas obligatoire mais c'est plus simple dans le cas d'un projet qui est entièrement géré par QSys.
En final un message doit apparaître dans lequel on vous dit qu'il faudra importer manuellement un fichier d'extension .qip dans votre projet Quartus... et donc retour à Quartus.
Retour dans Quartus
[modifier | modifier le wikicode]Cherchez votre fichier .qip dans un sous-répertoire "synthesis" et importez-le dans votre projet. Une fois votre réalisation matérielle importée dans Quartus, vous devez trouver un fichier VHDL. En voici l'entité :
-- niosii.vhd
-- Generated using ACDS version 15.0 145
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
entity niosii is
port (
clk_clk : in std_logic := '0'; -- clk.clk
s7segs_export : out std_logic_vector(7 downto 0) -- s7segs.export
);
end entity niosii;
Vous retrouvez, conformément au cahier des charges, une entrée horloge et une sortie "s7seg_export". Il vous resta à ajouter les contraintes.
Fichier de contraintes
[modifier | modifier le wikicode]Ce fichier de contrainte câble le segment g en poids faible et le segment a en poids 6. Ceci est important pour être capable de réaliser le trancodage correct.
To,Direction,Location,I/O Bank,VREF Group,I/O Standard,Reserved clk_clk,Input,PIN_Y2,2,B2_N0,3.3-V LVTTL, s7segs_export[0],Output,PIN_H22,6,B6_N0,2.5 V, s7segs_export[1],Output,PIN_J22,6,B6_N0,2.5 V, s7segs_export[2],Output,PIN_L25,6,B6_N1,2.5 V, s7segs_export[3],Output,PIN_L26,6,B6_N1,2.5 V, s7segs_export[4],Output,PIN_E17,7,B7_N2,2.5 V, s7segs_export[5],Output,PIN_F22,7,B7_N0,2.5 V, s7segs_export[6],Output,PIN_G18,7,B7_N2,2.5 V,
Vous avez certainement remarqué la différence entre le nom que vous avez donné s7segs et celui que vous trouvez dans les contraintes s7segs_export. C'est QSys qui fait automatiquement cet ajout de suffixe "_export". On l'a appris en examinant l'entité du fichier VHDL automatiquement généré.
Vous pouvez lancer la compilation matérielle... et il n'y a plus qu'a mettre des programmes dans tout cela.
Réalisation logicielle
[modifier | modifier le wikicode]Nous donnons d'abord le programme d'exemple que nous allons expliquer.
/*
* demo1.c
*
* Created on: Oct 1, 2016
* Author: serge
*/
#include "system.h"
#include "altera_avalon_pio_regs.h"
int main() {
int count = 0;
int delay;
unsigned char transcod[16]={0x01,0x4F,0x12,0x06,0x4C,0x24,0x20,0x0F,0x00,
0x04,0x08,0x60,0x31,0x42,0x30,0x38};
while(1) {
IOWR_ALTERA_AVALON_PIO_DATA(0x1000,transcod[count & 0x0F]);
delay = 0;
while(delay < 1000000) {
delay++;
}
count++;
}
return 0;
}
La compréhension des valeurs trouvées dans le tableau nécessite de connaître que l'organisation des segments est supposée être abcdefg, avec g poids faible. Il faut savoir aussi que pour allumer un segment sur notre carte, il faut un zéro logique.
Dans l'instruction IOWR_ALTERA_AVALON_PIO_DATA(0x1000,transcod[count & 0x0F]) apparaît la valeur 0x1000. C'est tout simplement l'adresse qui a été choisie pour l'adresse de notre PIO (voir figure ci-dessus).
Il est possible d'utiliser les types qui sont redéfinis dans "alt_types.h" qui permettent de préciser la taille des variables. En voici un exemple :
#include "alt_types.h"
int main (void)
{
alt_u32 time1;
A ce stade plusieurs questions peuvent vous venir à l'esprit mais nous y répondrons dans la section suivante :
- où dois-je écrire mon programme ?
- d'où viennent les fichiers d'inclusion system.h et altera_avalon_pio_regs.h ?
- d'où vient l'instruction IOWR_ALTERA_AVALON_PIO_DATA ?
Mettre le programme dans le NIOS
[modifier | modifier le wikicode]Nous avons assez d'expérience maintenant avec les processeurs enfouis pour savoir que c'est là que nous allons passer du temps. Comme les concepteurs de ces logiciels font tout pour nous cacher au maximum la complexité inhérente au matériel, au moment de l'écriture de ces lignes nous n'avons aucune idée sur la façon dont les choses se passent. Après quelques essais, il semble que le fichier .sof soit modifié lors d'une compilation, mais il nous faudra bien des investigations complémentaires pour tout comprendre. L'utilisateur final que vous êtes n'a pas besoin de comprendre ces détails.
Commençons par répondre aux questions de la section précédente.
- Où dois-je écrire mon programme ? Dans Eclipse. Vous y accéder de QSys dans Tools -> Nios II Software Buid Tools for Eclipse.
Eclipse, pour ceux qui ne connaissent pas, est un environnement où la notion de projet est centrale. Il est impossible de compiler un programme qui n'est pas dans un projet.
- D'où viennent les fichier d'inclusion ? C'est là où le fonctionnement est un peu particulier. Toutes les bibliothèques sont forcément dépendantes du matériel. Elles seront donc fabriquées au coup par coup, pour chaque projet matériel. Pour cela il faut faire un projet spécial à côté du projet précédent qui aura un nom forcément terminé par _bsp. C'est ce projet qui sera chargé de compiler toutes les bibliothèques et de générer tous les fichiers d'inclusion.
La réalisation des deux projets évoqués ci-dessus en une seule fois se fait avec :
- File
- New
- Nios II Application and BSP from template
- Il vous faudra alors renseigner le fichier .sopcinfo que vous désirez utiliser.
- Application Project
- Project name à renseigner
- Finish
- New
Garder le projet Hello_world proposé par défaut. Cela vous évitera d'ajouter un fichier dans le projet.
Évidemment changer le fichier Hello_world.c, par exemple avec le fichier C proposé plus haut.
- Clic droit sur le nom du projet
- Run as
- NiosII Hardware
- Run as
Cette commande compile le ficher .c fait une édition de lien avec les librairies nécessaires, et surtout charge automatiquement dans le FPGA.
Tout ce que l'on vient d'expliquer aboutit sur un fonctionnement assez proche à celui d'un microcontrôleur : on compile et l'ensemble se télécharge automatiquement. C'est ce que font les environnements à succès, comme Arduino, Energia, ATMEL studio, MPLABX, ...
Les choses semblent simples une fois que tout fonctionne et on se demande toujours pourquoi on y a passé autant de temps. En ce qui nous concerne c'est le téléchargement qui envoyait un message d'erreur. Même quand USB Blaster est correctement configuré, il faut un peu de patience pour que cela fonctionne... La relation USB Blaster et Eclipse est donc parfois un peu compliquée et demande donc du simple doigté et de la patience.
Aller plus loin
[modifier | modifier le wikicode]Que se passe-t-il si l'on désire ajouter un simple PIO matériel tout en restant dans le même projet. Que faut-il changer ?
La démarche est évidemment :
- de changer le projet QSys
- de générer le nouveau fichier .qip
- de retourner dans Quartus et d'ajouter les nouvelles contraintes pour le nouveau PIO et de compiler
- de télécharger le nouveau matériel dans la carte DE-115
- de retourner dans Eclipse
- de cliquer droit sur le projet *_bsp et choisir NIOS II -> generate BSP
- de modifier le fichier source pour tenir compte du nouveau matériel
- de compiler et charger dans la carte FPGA
Toutes ces étapes montrent qu'il faut mieux correctement prévoir son matériel dès le début. La modification logicielle est facile. La modification matérielle est elle aussi facile pour qui a un peu d'expérience. Mais il est facile d'attraper le tournis pour les modifications des deux en même temps.
Le cliquer droit sur le projet *_bsp et choisir NIOS II -> generate BSP fonctionne correctement tant que l'on ne quitte pas Eclipse. Lors d'un redémarrage avec une idée nouvelle pour le matériel, il nous a souvent fallu effacer les projets et les refaire pour aboutir sur un fonctionnement correct, comme s'il perdait la relation avec le fichier .sopcinfo !
Problèmes rencontrés
[modifier | modifier le wikicode]- Ceux qui ont examinés avec attention la figure proposée pour illustrer la partie matérielle réalisée avec QSys auront remarqué que le N.I.O.S. apparaît avant la RAM, contrairement à ce qui se passe si vous suivez l'ordre des explications dans le texte ci-dessus. Si vous faites le nios en premier, il faudra revenir dessus une fois la RAM choisie pour positionner correctement le Reset vector Memory et le Exception_vector. Sinon, il vous restera deux erreurs en rouge.
- Si Eclipse vous dit qu'il a rencontré un problème pour mettre le fichier ELF dans le processeur c'est que probablement il n'a pas réussi à utiliser USB Blaster. Personnellement nous chargeons le processeur avec Quartus et ne fermons pas le Programmer et Eclipse le retrouve.
- A ce stade de nos investigations, nous ne savons pas s'il faut que le processeur soit dans la carte pour charger le programme en mémoire.
Timer en interruption
[modifier | modifier le wikicode]Nous avons comme objectif, dans cette section de faire fonctionner un simple TIMER avec une interruption. Après avoir présenté sa réalisation matérielle, nous nous intéresserons au fonctionnement de celui-ci puis, pour finir, nous présenterons un programme de mise en œuvre.
Le matériel : réalisation avec QSys
[modifier | modifier le wikicode]L'ajout d'un Timer se fait dans QSys. La première utilisation d'un timer est un peu déroutante même si (surtout si) vous avez de l'expérience avec les Timers des petits microcontrôleurs.
- Ceux-ci sont dotés de pré-diviseurs, mais ce n'est pas le cas ici. Cela est lié au fait qu'on est dans le domaine des 32 bits. 32 bits commandés par 50 MHz font une période de 86 secondes environ, ce qui est largement suffisant pour ce que l'on a à faire couramment.
- Le panneau de configuration du timer que l'on veut ajouter fait apparaître une période configurable. Celle-ci sera prise en compte seulement si vous cochez Fixed period un peu plus bas.
Une première image vous montre la configuration de notre timer.
La partie de droite de la figure vous montre une configuration de la période à 100 ms. Pour la deuxième fois nous répétons que cette période est prise en compte dans le seul cas où vous avez coché dans Registers l'option Fixed period ce qui n'est pas notre cas.
La partie Output signals vous permet
- de choisir un fonctionnement de type chien de garde (watchdog)
- de sortir un bit qui est à un pendant une période d'horloge lors d'un dépassement de capacité
Aucune de ces options n'est utilisée par notre projet.
Voici ce que cela donne une fois réalisé dans QSys.
Remarquez à droite l'interruption n°31 qui sera déclenchée par le dépassement de capacité du timer. La priorité des interruptions décroit quand le numéro d'interruption croit. On a donc choisi une faible priorité car cette interruption est la seule pour le moment.
Maintenant que le TIMER est présent dans le matériel, il nous faut comprendre comment il fonctionne.
Fonctionnement et description du TIMER
[modifier | modifier le wikicode]Le timer est composé de registres. Leur nombre dépend du choix du type de timer : timer sur 32 bits ou timer sur 64 bits. En ce qui nous concerne nous allons nous intéresser au timer sur 32 bits. Il est composé des registres 16 bits suivants :
- status
- registre de contrôle
- registre PERIODL
- registre PERIODH
- registre SNAPL
- registre SNAPH
Comme vous faites du codesign (matériel et logiciel) il est important de savoir naviguer dans les fichiers d'entête pour savoir comment les choses ont été définies pour vous. Comme nous parlons du timer, l'ajout d'un tel périphérique provoquera automatiquement l'apparition d'un fichier d'entête appelé altera_avalon_timer_regs.h. Cette dernière phrase pourrait laisser entendre que ce fichier est automatiquement ajouté à votre programme mais ce n'est pas le cas. C'est à vous de l'ajouter dans les #include. Une fois ajouté, vous le voyez dans la partie droite d'Eclipse, vous double cliquez pour l'ouvrir.
Les accès aux registres demandent simplement de connaître l'adresse de base de votre périphérique. Comme nous n'avons rien changé à ce qui nous était proposé par défaut, notre Timer se trouve en adresse 0. Notre base sera donc 0.
Registre de status
[modifier | modifier le wikicode]La partie du fichier d'entête altera_avalon_timer_regs.h correspondante est :
/* STATUS register */
#define ALTERA_AVALON_TIMER_STATUS_REG 0
#define IOADDR_ALTERA_AVALON_TIMER_STATUS(base) \
__IO_CALC_ADDRESS_NATIVE(base, ALTERA_AVALON_TIMER_STATUS_REG)
#define IORD_ALTERA_AVALON_TIMER_STATUS(base) \
IORD(base, ALTERA_AVALON_TIMER_STATUS_REG)
#define IOWR_ALTERA_AVALON_TIMER_STATUS(base, data) \
IOWR(base, ALTERA_AVALON_TIMER_STATUS_REG, data)
#define ALTERA_AVALON_TIMER_STATUS_TO_MSK (0x1)
#define ALTERA_AVALON_TIMER_STATUS_TO_OFST (0)
#define ALTERA_AVALON_TIMER_STATUS_RUN_MSK (0x2)
#define ALTERA_AVALON_TIMER_STATUS_RUN_OFST (1)
La deuxième ligne indique qu'il est en adresse 0 par rapport à la base.
La cinquième et septième ligne indiquent que ce registre est accessible en lecture IORD_ALTERA_AVALON_TIMER_STATUS(base) et en écriture IOWR_ALTERA_AVALON_TIMER_STATUS(base, data). Les lignes suivantes définissent des noms symboliques pour les deux bits utiles du registre :
- ALTERA_AVALON_TIMER_STATUS_TO_MSK définit le bit de poids faible qui s'appelle TO (Timer Overflow)
- ALTERA_AVALON_TIMER_STATUS_RUN_MSK définit le bit à sa gauche qui est à un quand le timer est en marche.
Vous pouvez utiliser les OFST avec des décalages en lieu et place des masques.
Le bit TO (poids faible) passe à un lors d'un dépassement de capacité, mais c'est à vous, programmeur de le remettre à 0. Ceci devra être réalisé dans chaque routine d'interruption.
Réaliser un code qui ne modifie qu'un seul bit ne se fait qu'en trois étapes :
// declarer la variable status comme : alt_u16 status;
status = IORD_ALTERA_AVALON_TIMER_STATUS(0);
status &= ~ALTERA_AVALON_TIMER_STATUS_TO_MSK;
// met 0 dans TO sans changer les autres bits
IOWR_ALTERA_AVALON_TIMER_STATUS(0, status);
Ici comme un seul bit est accessible en écriture on peut se contenter de :
IOWR_ALTERA_AVALON_TIMER_STATUS(0, ~ALTERA_AVALON_TIMER_STATUS_TO_MSK);
Le premier paramètre est ici à 0 car notre base est à 0.
Registre de contrôle
[modifier | modifier le wikicode]La partie du fichier d'entête altera_avalon_timer_regs.h correspondante est :
/* CONTROL register */
#define ALTERA_AVALON_TIMER_CONTROL_REG 1
#define IOADDR_ALTERA_AVALON_TIMER_CONTROL(base) \
__IO_CALC_ADDRESS_NATIVE(base, ALTERA_AVALON_TIMER_CONTROL_REG)
#define IORD_ALTERA_AVALON_TIMER_CONTROL(base) \
IORD(base, ALTERA_AVALON_TIMER_CONTROL_REG)
#define IOWR_ALTERA_AVALON_TIMER_CONTROL(base, data) \
IOWR(base, ALTERA_AVALON_TIMER_CONTROL_REG, data)
#define ALTERA_AVALON_TIMER_CONTROL_ITO_MSK (0x1)
#define ALTERA_AVALON_TIMER_CONTROL_ITO_OFST (0)
#define ALTERA_AVALON_TIMER_CONTROL_CONT_MSK (0x2)
#define ALTERA_AVALON_TIMER_CONTROL_CONT_OFST (1)
#define ALTERA_AVALON_TIMER_CONTROL_START_MSK (0x4)
#define ALTERA_AVALON_TIMER_CONTROL_START_OFST (2)
#define ALTERA_AVALON_TIMER_CONTROL_STOP_MSK (0x8)
#define ALTERA_AVALON_TIMER_CONTROL_STOP_OFST (3)
Il montre que ce registre contient 4 bits de contrôle (donnés ici du poids faible vers le poids fort) :
- ITO : Interrupt Timer Overflow est un bit d'autorisation d'interruption
- CONT : permet l'utilisation du timer en continu s'il est positionné à 1, autrement le timer s'arrête au premier dépassement de capacité
- START : démarre le TIMER, comme son nom l'indique, dès qu'il est positionné à 1
- STOP : arrête le timer.
Le démarrage du timer en mode continu se fait avec l'instruction (avec fichier d'entête altera_avalon_timer_regs.h) :
IOWR_ALTERA_AVALON_TIMER_CONTROL(0 , ALTERA_AVALON_TIMER_CONTROL_START_MSK
| ALTERA_AVALON_TIMER_CONTROL_ITO_MSK
| ALTERA_AVALON_TIMER_CONTROL_CONT_MSK);
Encore une fois le premier paramètre est à 0 car notre base est à 0.
Registres PERIODL et PERIODH
[modifier | modifier le wikicode]Comme leurs noms l'indiquent, ces deux registres sont là pour déterminer le début de décomptage. En effet ce timer fonctionne avec rechargement de sa valeur avec lorsqu'il y a eu dépassement de capacité.
Ces deux registres sont disponibles en écriture et en lecture.
L'écriture d'une valeur particulière se fait donc par :
IOWR_ALTERA_AVALON_TIMER_PERIODL(0,0xFFFF);
IOWR_ALTERA_AVALON_TIMER_PERIODH(0,0x00FF);
qui met 0x00FFFFFF comme valeur à charger à chaque dépassement de capacité.
Registres SNAPL et SNAPH
[modifier | modifier le wikicode]Ces deux registres contiennent la valeur courante du TIMER.
Ces deux registres sont disponibles en écriture et en lecture.
Résumé du fonctionnement du Timer
[modifier | modifier le wikicode]La figure ci-contre résume les registres à utiliser pour mettre œuvre le périphérique Interval Timer. Ce qu'il est important d'avoir à l'esprit, est que les sous-programmes montrés à droite de chaque registre ont comme premier paramètre l'adresse que vous avez choisie dans QSys. Pour nous c'est 0, mais cela peut être différent pour vous.
L'autre point important à retenir qui n'apparaît pas dans la figure est que le bit TO du registre status est un drapeau (flag). Comme toujours, ce genre de bit est mis à un par le matériel, mais c'est au programmeur de le mettre à 0.
Enfin dernier point, ces registres sont sur 16 bits comme le montre la figure.
Il est grand temps d'écrire le programme complet maintenant.
Le logiciel
[modifier | modifier le wikicode]Les constantes utilisées dans ce code sont définies dans "system.h".
Le code présenté ci-dessous fonctionne correctement. Il est possible de changer la rapidité de comptage en changeant le paramètre de l'instruction
IOWR_ALTERA_AVALON_TIMER_PERIODH(0,0x00FF);
Le débit des interruptions est donc maintenant réglable par une simple valeur dans un registre.
#include "system.h"
#include "altera_avalon_pio_regs.h"
#include "altera_avalon_timer_regs.h"
#include <sys/alt_irq.h>
#include "alt_types.h"
volatile alt_u32 count = 0;
alt_u32 *timer_ptr = (alt_u32 *)TIMER_0_BASE;
static void count_increment_isr(void* context, alt_u32 id)
{
IOWR_ALTERA_AVALON_TIMER_STATUS(0, ~ALTERA_AVALON_TIMER_STATUS_TO_MSK);
count++; // increment count
}
int main() {
int delay;
unsigned char transcod[16]={0x01,0x4F,0x12,0x06,0x4C,0x24,0x20,0x0F,0x00,
0x04,0x08,0x60,0x31,0x42,0x30,0x38};
//alt_irq_disable_all();
IOWR_ALTERA_AVALON_TIMER_PERIODL(0,0xFFFF);
IOWR_ALTERA_AVALON_TIMER_PERIODH(0,0x00FF);
IOWR_ALTERA_AVALON_TIMER_CONTROL(0,ALTERA_AVALON_TIMER_CONTROL_START_MSK
| ALTERA_AVALON_TIMER_CONTROL_ITO_MSK | ALTERA_AVALON_TIMER_CONTROL_CONT_MSK);
//alt_irq_enabled();
alt_ic_isr_register(TIMER_0_IRQ_INTERRUPT_CONTROLLER_ID,TIMER_0_IRQ,
count_increment_isr, timer_ptr, 0x0);
while(1) {
IOWR_ALTERA_AVALON_PIO_DATA(0x1000,transcod[count & 0x0F]);
delay = 0;
while(delay < 10000) {
delay++;
}
}
return 0;
}
Nous avons gardé l'attente passive dans le programme principal mais ce n'est plus elle qui rythme le comptage, c'est l'interruption. En effet l'instruction count++ est bien dans l'interruption. L'affichage par contre, est réalisé dans le programme principal.
Périphérique d'écriture LCD pour carte DE2-115
[modifier | modifier le wikicode]Nous allons essayer dans cette section un périphérique de gestion d'afficheur LCD 2 lignes de 16 caractères. Ce périphérique est présent sur la carte DE2-115 de Terasic. Un gestionnaire matériel de ce périphérique est disponible lorsque l'on utilise QSys. Seule sa documentation laisse à désirer, et peut être aussi sa librairie C. Mais ce dernier point est peut être dû à un manque d'expérience de notre part.
Réalisation matérielle
[modifier | modifier le wikicode]Après avoir fait le processeur et sa mémoire on ajoute un PIO. Il servira essentiellement à allumer des LEDs pour voir si le programme C tourne normalement. Il est positionné en adresse 0x0000.
On ajoutera en plus un périphérique destiné à la gestion de l'écran lcd.
- processors and peripheral
- Peripherals
- Altera Avalon LCD 16207
- Peripherals
Il sera positionné en adresse 0x0020.
Réalisation logicielle
[modifier | modifier le wikicode]Le code source donné ci-dessous explore les possibilités d'utilisation de l'afficheur lcd. Il est complètement fonctionnel. Mais faute de documentation convaincante, nous avons tenté de reproduire les commandes classiques d'un gestionnaire LCD en nous aidant de la librairie Arduino...
Le programme donné en exemple affiche un "BONJOUR" qui se déplace sur la première ligne et un " A TOUS" sur la deuxième ligne.
/*
* "Hello World" example.
*
* This example prints 'Hello from Nios II' to the STDOUT stream. It runs on
* the Nios II 'standard', 'full_featured', 'fast', and 'low_cost' example
* designs. It runs with or without the MicroC/OS-II RTOS and requires a STDOUT
* device in your system's hardware.
* The memory footprint of this hosted application is ~69 kbytes by default
* using the standard reference design.
*
* For a reduced footprint version of this template, and an explanation of how
* to reduce the memory footprint for a given application, see the
* "small_hello_world" template.
*
*/
//#include <stdio.h>
#include "system.h"
#include "altera_avalon_pio_regs.h"
#include "altera_avalon_lcd_16207_regs.h"
#include "altera_avalon_lcd_16207.h"
#include "alt_types.h"
#define LCD_BASE 0x0020
void delai(int delay);
void clearScreen();
void setCursor(alt_u8 col, alt_u8 row);
void writecar(char car);
void writestr(char *chaine);
void Autoscroll(void);
altera_avalon_lcd_16207_state sp;
int main()
{ // variables
alt_u16 cmpt;
alt_u8 posx;
// setup
//altera_avalon_lcd_16207_init(&sp);
cmpt = 0;
// loop
while(1) {
for (posx=0;posx<8;posx++) {
//setCursor(0, 0);
//writestr(" ");// efface première ligne
clearScreen(); //OK
setCursor(posx, 0);
//lcd_clear_screen(&sp); //Pb edition de liens
writestr("BONJOUR");
setCursor(posx, 1);
writestr(" A TOUS");
IOWR_ALTERA_AVALON_PIO_DATA(0x0000,cmpt);
cmpt++;
delai(1000000);}
}
return 0;
}
void delai(int delay) {
int delai = 0;
while(delai < delay) {
delai++;
}
}
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CLEARDISPLAY 0x01
//#define LCD_DISPLAYON 0x04
void clearScreen() {
// clear display, set cursor position to zero
IOWR_ALTERA_AVALON_LCD_16207_COMMAND(LCD_BASE, LCD_CLEARDISPLAY);
delai(100000); // this command takes a long time!
}
#define LCD_SETDDRAMADDR 0x80
void setCursor(alt_u8 col, alt_u8 row)
{
col = col & 0x0F; // %16
row = row & 0x01; // %2
//command(LCD_SETDDRAMADDR | (col + 0x40*row));
IOWR_ALTERA_AVALON_LCD_16207_COMMAND(LCD_BASE, LCD_SETDDRAMADDR | (col + 0x40*row));
delai(100);
}
#define LCD_ENTRYMODESET 0x04
#define LCD_ENTRYSHIFTINCREMENT 0x01
void Autoscroll(void) {
// alt_u8 displaymode=0;
// displaymode |= LCD_ENTRYSHIFTINCREMENT;
IOWR_ALTERA_AVALON_LCD_16207_COMMAND(LCD_BASE,(LCD_ENTRYMODESET | LCD_ENTRYSHIFTINCREMENT));
}
void writecar(char car) {
IOWR_ALTERA_AVALON_LCD_16207_DATA(LCD_BASE, car);
}
void writestr(char *chaine) {
while (*chaine) {
writecar((char)*chaine++);
delai(100);
}
}
Nous avons refait les commandes "clearScreen" et "setCursor" bien utiles. La première est pourtant présente sous le nom "lcd_clear_screen" dans
- PROJET_BSP -> drivers -> src -> altera_avalon_lcd_16207.c
Le problème est que altera_avalon_lcd_16207.h ne met pas toutes les fonctions à disposition et nous ne comprenons pas pourquoi ! Dès que nous utilisons une fonction intéressante de altera_avalon_lcd_16207.c, nous nous trouvons avec un message d'erreur à l'édition de liens. Nous avons pourtant bien vérifié la présence de ce fichier dans le makeFile !!
Nous avons un peu enrichi la librairie en proposant un "scrollDisplayLeft" et un "scrollDisplayRight" qui ont l'avantage de déplacer l'affichage sans changer le contenu de la mémoire d'affichage.
#include "system.h"
#include "altera_avalon_pio_regs.h"
#include "altera_avalon_lcd_16207_regs.h"
#include "altera_avalon_lcd_16207.h"
#include "alt_types.h"
#define LCD_BASE 0x0020
void delai(int delay);
void clearScreen();
void setCursor(alt_u8 col, alt_u8 row);
void writecar(char car);
void writestr(char *chaine);
void scrollDisplayLeft(void);
void scrollDisplayRight(void);
int main()
{ // variables
alt_u16 cmpt;
alt_u8 posx;
// setup
cmpt = 0;
// loop
while(1) {
clearScreen();
setCursor(0, 0);
writestr("BONJOUR");
setCursor(0, 1);
writestr(" A TOUS");
for (posx=0;posx<8;posx++) {
scrollDisplayRight();
IOWR_ALTERA_AVALON_PIO_DATA(0x0000,cmpt);
cmpt++;
delai(1000000);
} // for
} // while(1)
return 0;
} // main
void delai(int delay) {
int delai = 0;
while(delai < delay) {
delai++;
}
}
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CLEARDISPLAY 0x01
//#define LCD_DISPLAYON 0x04
void clearScreen() {
// clear display, set cursor position to zero
IOWR_ALTERA_AVALON_LCD_16207_COMMAND(LCD_BASE, LCD_CLEARDISPLAY);
delai(100000); // this command takes a long time!
}
#define LCD_SETDDRAMADDR 0x80
void setCursor(alt_u8 col, alt_u8 row)
{
col = col & 0x0F; // %16
row = row & 0x01; // %2
//command(LCD_SETDDRAMADDR | (col + 0x40*row));
IOWR_ALTERA_AVALON_LCD_16207_COMMAND(LCD_BASE, LCD_SETDDRAMADDR | (col + 0x40*row));
delai(100);
}
#define LCD_CURSORSHIFT 0x10
#define LCD_DISPLAYMOVE 0x08
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00
// These commands scroll the display without changing the RAM
void scrollDisplayLeft(void) {
//command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
IOWR_ALTERA_AVALON_LCD_16207_COMMAND(LCD_BASE,LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}
void scrollDisplayRight(void) {
//command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
IOWR_ALTERA_AVALON_LCD_16207_COMMAND(LCD_BASE,LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}
void writecar(char car) {
IOWR_ALTERA_AVALON_LCD_16207_DATA(LCD_BASE, car);
}
void writestr(char *chaine) {
while (*chaine) {
writecar((char)*chaine++);
delai(100);
}
}
Notre façon de définir les constantes juste avant leurs utilisations n'est pas habituelle. Mais la partie de sous-programmes utiles est tellement petite que nous n'avons pas décidé de regrouper toutes les constantes en début de programme.
La seule primitive manquante serait le "createChar" de la librairie Arduino qui permettrait de réaliser ses propres caractères d'affichage. Nous laissons cela au lecteur intéressé sous forme d'exercice.
Exercice
[modifier | modifier le wikicode]On vous demande de modifier les programmes d'exemple ci-dessus pour ajouter la primitive "createChar" à vos sous-programmes utiles. Voici son code source dans la librairie Arduino :
// Allows us to fill the first 8 CGRAM locations
// with custom characters
void LiquidCrystal::createChar(uint8_t location, uint8_t charmap[]) {
location &= 0x7; // we only have 8 locations 0-7
command(LCD_SETCGRAMADDR | (location << 3));
for (int i=0; i<8; i++) {
write(charmap[i]);
}
}
On ne gardera pas la notion d'objet utilisée dans cette librairie Arduino.
#include "system.h"
#include "altera_avalon_pio_regs.h"
#include "altera_avalon_lcd_16207_regs.h"
#include "altera_avalon_lcd_16207.h"
#include "alt_types.h"
#define LCD_BASE 0x0020
void delai(int delay);
void clearScreen();
void setCursor(alt_u8 col, alt_u8 row);
void writecar(char car);
void writestr(char *chaine);
void scrollDisplayLeft(void);
void scrollDisplayRight(void);
void createChar(alt_u8 location, alt_u8 charmap[]);
alt_u8 heart[8] = { // from Arduino Library
0b00000,
0b01010,
0b11111,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000
};
int main()
{ // variables
alt_u16 cmpt;
alt_u8 posx;
// setup
cmpt = 0;
createChar(0, heart); // custum heart character
// loop
while(1) {
clearScreen();
setCursor(0, 0);
writestr("BONJOUR");
setCursor(0, 1);
writestr(" A TOUS");writecar(0); // heart used here
for (posx=0;posx<8;posx++) {
scrollDisplayRight();
IOWR_ALTERA_AVALON_PIO_DATA(0x0000,cmpt);
cmpt++;
delai(1000000);
} // for
} // while(1)
return 0;
} // main
void delai(int delay) {
int delai = 0;
while(delai < delay) {
delai++;
}
}
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CLEARDISPLAY 0x01
//#define LCD_DISPLAYON 0x04
void clearScreen() {
// clear display, set cursor position to zero
IOWR_ALTERA_AVALON_LCD_16207_COMMAND(LCD_BASE, LCD_CLEARDISPLAY);
delai(100000); // this command takes a long time!
}
#define LCD_SETDDRAMADDR 0x80
void setCursor(alt_u8 col, alt_u8 row)
{
col = col & 0x0F; // %16
row = row & 0x01; // %2
//command(LCD_SETDDRAMADDR | (col + 0x40*row));
IOWR_ALTERA_AVALON_LCD_16207_COMMAND(LCD_BASE, LCD_SETDDRAMADDR | (col + 0x40*row));
delai(100);
}
#define LCD_CURSORSHIFT 0x10
#define LCD_DISPLAYMOVE 0x08
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00
// These commands scroll the display without changing the RAM
void scrollDisplayLeft(void) {
//command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
IOWR_ALTERA_AVALON_LCD_16207_COMMAND(LCD_BASE,LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}
void scrollDisplayRight(void) {
//command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
IOWR_ALTERA_AVALON_LCD_16207_COMMAND(LCD_BASE,LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}
#define LCD_SETCGRAMADDR 0x40
// Allows us to fill the first 8 CGRAM locations
// with custom characters
void createChar(alt_u8 location, alt_u8 charmap[]) {
alt_u8 i;
location &= 0x7; // we only have 8 locations 0-7
IOWR_ALTERA_AVALON_LCD_16207_COMMAND(LCD_BASE,LCD_SETCGRAMADDR | (location << 3));
for (i=0; i<8; i++) {
writecar((char)charmap[i]);delai(100);
}
}
void writecar(char car) {
//while(IORD_ALTERA_AVALON_LCD_16207_STATUS(LCD_BASE)&ALTERA_AVALON_LCD_16207_STATUS_BUSY_MSK);
IOWR_ALTERA_AVALON_LCD_16207_DATA(LCD_BASE, car);
}
void writestr(char *chaine) {
while (*chaine) {
writecar((char)*chaine++);
delai(100);
}
}
Voir aussi
[modifier | modifier le wikicode]Youtube et Internet
[modifier | modifier le wikicode]- Implementing Nios II on DE10-Lite - SDSU EE492
- max10 sdram nios test (max10 de10 lite board)
- GitHub de10 lite board
Livres et documents
[modifier | modifier le wikicode]Il est facile de trouver des livres (même en français) qui traitent du NIOS II avec plus ou moins de détails.
- A. ATTOUI "Architecture des systèmes sur puce : Processeurs synthétisables, CAO VLSI, Norme VCI, environnements ISE et Quartus", Ellipses (2005) se consacre, entre autres, à Altera et NIOS et (en) AMBA
- Z. Navabi "Embedded Core design with FPGA", McGraw-Hill (2007), assez général mais les applications sont Altera et NIOSII et en Verilog.
- P. P. Chu "Embedded SoPC Design with NIOS II Processor and VHDL Examples", WILEY (2011)