info2 - tp n°5 utilisation de l'outil de debug keil avec...

12
INFO2 TP5 page 1 INFO2 - TP n°5 Utilisation de l'outil de debug Keil avec la carte mbed LPC1768 PARTIE 1 – Présentation et Installation de l'outil Keil Pour faire le debug d'un code développé pour une plateforme embarqué comme le mbed LPC1768, il y a deux solutions: faire des affichages sur une console ou bien utiliser un environnement de debug. La première solution est simple à mettre en œuvre (il s'agit d'ajouter des printf dans le code source) mais demeure peu pratique et peu efficace. Dans ce TP n°5 nous allons opter pour la seconde solution en utilisant l'outil de debug Keil disponible pour les processeurs ARM. Cet outil s'utilise très simplement depuis le port USB de la carte LPC1768. L'outil Keil permet de visualiser le code généré par le compilateur (le code assembleur) ainsi que les différents registres du processeur ARM Cortex- M3 ou encore de mettre des points d'arrêts dans le code (breakpoints). 1) Faire un upgrade du bootloader (compatibilité avec l'interface CMSIS-DAP) Avant de pouvoir lancer l'outil Keil avec la plateforme LPC1768 il faut s'assurer que la version du bootloader est compatible. En effet, l'interface CMSIS-DAP utilisée pour reliée le LPC1768 à l'outil Keil n’est supportée que pour la version 141212 du bootloader. Remarque : Le bootloader est le programme lancé au démarrage de votre plateforme. Ce programme est stocké dans la mémoire Flash du LP1768. Pour effectuer un upgrade du bootloader (flasher votre mbed), suivre les instructions indiquées sur ce lien: http://developer.mbed.org/handbook/Firmware-LPC1768-LPC11U24 2) Installer les outils Keil Les outils Keil (la MDK Version 5) sont déjà installés en salle 3A. Si jamais les outils ne sont pas installés sur votre machine, voici le lien pour trouver les outils à installer : http://www.keil.com/dd2/nxp/lpc1768/ Remarque : une inscription est nécessaire pour télécharger les outils Keil. Important : c'est une version d’évaluation avec certaines restrictions comme par exemple la taille programme + données qui est limitée à 32 kb (mais c'est largement suffisant pour ce que nous aurons à faire dans ce TP).

Upload: nguyennga

Post on 14-Sep-2018

220 views

Category:

Documents


0 download

TRANSCRIPT

INFO2 TP5 page 1

INFO2 - TP n°5 Utilisation de l'outil de debug Keil avec la carte mbed LPC1768

PARTIE 1 – Présentation et Installation de l'outil Keil

Pour faire le debug d'un code développé pour une plateforme embarqué comme le mbed LPC1768, il y a deux solutions:

faire des affichages sur une console ou bien utiliser un environnement de debug.

La première solution est simple à mettre en œuvre (il s'agit d'ajouter des printf dans le code source) mais demeure peu pratique et peu efficace. Dans ce TP n°5 nous allons opter pour la seconde solution en utilisant l'outil de debug Keil disponible pour les processeurs ARM. Cet outil s'utilise très simplement depuis le port USB de la carte LPC1768. L'outil Keil permet de visualiser le code généré par le compilateur (le code assembleur) ainsi que les différents registres du processeur ARM Cortex-M3 ou encore de mettre des points d'arrêts dans le code (breakpoints).

1) Faire un upgrade du bootloader (compatibilité avec l'interface CMSIS-DAP)

Avant de pouvoir lancer l'outil Keil avec la plateforme LPC1768 il faut s'assurer que la version du bootloader est compatible. En effet, l'interface CMSIS-DAP utilisée pour reliée le LPC1768 à l'outil Keil n’est supportée que pour la version 141212 du bootloader. Remarque : Le bootloader est le programme lancé au démarrage de votre plateforme. Ce programme est stocké dans la mémoire Flash du LP1768. Pour effectuer un upgrade du bootloader (flasher votre mbed), suivre les instructions indiquées sur ce lien: http://developer.mbed.org/handbook/Firmware-LPC1768-LPC11U24

2) Installer les outils Keil

Les outils Keil (la MDK Version 5) sont déjà installés en salle 3A. Si jamais les outils ne sont pas installés sur votre machine, voici le lien pour trouver les outils à installer : http://www.keil.com/dd2/nxp/lpc1768/ Remarque : une inscription est nécessaire pour télécharger les outils Keil. Important : c'est une version d’évaluation avec certaines restrictions comme par exemple la taille programme + données qui est limitée à 32 kb (mais c'est largement suffisant pour ce que nous aurons à faire dans ce TP).

INFO2 TP5 page 2

PARTIE 2 – Prise en main de l'outil Keil

3) Exporter un projet mbed sur la cible Keil µVision

Une fois l'outil Keil installé, nous allons exporter (pour la cible µVision) un projet sous l’environnement en ligne de mbed (clic droit sur votre projet et faire "Export Program…". Cette étape génère un .ZIP qu’il faut décompresser où vous voulez. Créer un projet sous l'environnement de développement mbed (mbed.org) et écrire le programme suivant qui permet de faire clignoter une LED. // Chenillard sur les 4 leds de la carte mbed

#include "mbed.h"

DigitalOut myled[4]= {LED1, LED2, LED3, LED4};

Serial pc(USBTX, USBRX);

int main()

{

pc.printf("Chenillard...\n");

int leds = 0x1;

int i = 0;

while(1) {

myled[i] = 1; // Led ON

wait(0.2); // unit in second

myled[i++] = 0; // Led ON

leds = (leds<<1);

if (leds == 0x10) {

leds = 1;

i=0;

}

}

}

4) Lancer l'outil Keil µVision

Lancer les outils Keil (µVision) et ouvrir le projet qui a été généré depuis mbed en ligne (Project-> Open Project). Le projet a une extension .uvproj (cliquer sur "Migrate to Device Pack" afin de générer un projet compatible avec la version de Keil). Il faut à présent vérifier la configuration de l'outil Keil pour la cible LPC1768. Depuis l’IDE Keil, allez dans le menu

a. Flash-> Configure Flash Tools…

INFO2 TP5 page 3

b. Depuis l’onglet « Utilities », sélectionner “CMSIS-DAP Debugger” pour « Use Target Driver for Flash Programming »

c. Ensuite cliquer sur “Settings” puis “Add” d. Il reste à choisir la cible, ici LPC17xx

e. Avant de lancer le debugger il est nécessaire de compiler le projet, pour cela aller dans "Project -> Build Target" ou plus simplement en cliquant sur la touche F7. Cette opération crée un exécutable compatible pour le mode debug et dont l'extension du fichier est ".axf".

INFO2 TP5 page 4

f. Vérifier également la configuration suivante Dans le menu Flash -> Configure Flash Tools…, Puis dans l’onglet Utilities CMSIS-DAP Debugger -> Settings -> Debug:

Remarque : Pour Reset vous pouvez choisir HW RESET ou Autodetect.

5) Il reste à lancer le debugger : Menu Debug -> Start/Stop Debug Session.

Si tout se passe bien vous pouvez visualiser le code désassemblé, la mémoire, les registres, mettre des point d’arrêts, des watch de variables, etc… bref faire du debug. Mais nous verrons cela dans la partie 3 de ce TP. Pour lancer l’exécution du programme il suffit de faire F5 (ou Debug-> Run). Vérifier que le programme s'exécute correctement (chenillard avec les leds de la carte mbed).

Vérifier que la connexion au Terminal pour les printf/scanf est bien fonctionnelle. Tout ce que nous venons de voir (ou presque) est résumé sur cette page : http://developer.mbed.org/handbook/CMSIS-DAP-MDK

Code désassemblé

Code C

Les registres

INFO2 TP5 page 5

PARTIE 3 – Exécuter du code en mode Debug

6) Mettre des points d'arrêts dans le code et faire une exécution pas à pas

a) Les points d'arrêts (breakpoints) Grâce à l'outil Keil, nous pouvons mettre un point d'arrêt (breakpoint) dans le code C. Un breakpoint permet de stopper l'exécution d'un programme à un endroit précis Remarque : il est également possible de stopper l'exécution lorsqu'un programme accède à une donnée en mémoire en lecture ou écriture. Pour mettre un breakpoint il suffit de cliquer à gauche dans la fenêtre affichant le code C à l'endroit où l'on souhaite s'arrêter. Un petit rond rouge apparait alors indiquant que le breakpoint a bien été posé (cf. Figure ci-dessous).

Lancer l'exécution du programme en appuyant successivement sur la touche F5 (i.e. Run). Que constatez-vous? Expliquez. b) L'exécution pas à pas (step by step) La touche F5 permet de lancer l'exécution du programme. Dans ce cas, l'exécution ne s'arrête que si le programme rencontre un point d'arrêt ou lorsqu'il se termine. Or, il est parfois bien utile de faire une exécution du code pas à pas (step-by-step en anglais) afin de bien analyser et comprendre ce qui se passe…

Le breakpoint a été posé

Les différentes possibilités de faire

une exécution pas à pas

INFO2 TP5 page 6

c) Différence entre les différents modes d'exécution pas à pas Expliquer la différence entre les différentes possibilités d'exécution pas à pas :

- Step - Step over - Step out - Step to cursor line

Quelles sont les touches raccourcis pour ces différentes possibilités d'exécution pas à pas? Faites une exécution du code en utilisant les différentes possibilités d'exécution pas à pas.

7) Visualiser le contenu de la mémoire de données

Saisir le programme suivant depuis mbed.org. Une fois votre projet compilé, exportez le projet afin de l'ouvrir dans l'environnement de debug Keil. // Addition de 2 entiers

// ---------------------

#include "mbed.h"

Serial pc(USBTX,USBRX);

int iOp1, iOp2, iResult ;

int main()

{

pc.printf("Debut du programme...") ;

iOp1 = 0x1;

iOp2 = 0x4;

do {

iResult = iOp1 + iOp2 ;

pc.printf("Operand 1 = %d\n",iOp1) ;

pc.printf("Operand 2 = %d\n",iOp2) ;

pc.printf("Resultat = %d\n",iResult) ;

}

while (iResult != 0);

pc.printf("Fin du programme\n") ;

}

a) Déterminer les adresses en mémoire Depuis l'outil de debug Keil et en vous servant de la fenêtre "Symbols" (Menu View->Symbols Window), déterminer les adresses en mémoire où sont stockées les variables iOp1, iOp2 et iResult.

Variable Adresse

iOp1

iOp2

iResult

En déduire l'espace utilisé pour stocker chacune des variables en mémoire. b) Visualiser le contenu des données en mémoire

Depuis l'outil de debug Keil et en vous servant de la fenêtre "Memory" (Menu View -> Memory Windows -> Memory 1), visualisez le contenu de ces 3 variables en effectuant une exécution pas à pas du programme.

INFO2 TP5 page 7

c) Ajouter des watch sur des variables

Il est également possible de visualiser le contenu des variables en utilisant des "watch". Depuis l'outil de debug Keil et en vous servant de la fenêtre "Watch" (Menu View-> Watch Windows-> Watch 1), visualisez le contenu de ces 3 variables en effectuant une exécution pas à pas du programme. d) Forcer la valeur des variables iOp1 et iOp2 Depuis les fenêtres "Watch 1" ou "Memory 1", modifier les valeurs des opérandes iOp1 et iOp2. Faites une exécution pas à pas du programme afin de vérifier que les variables ont bien été modifiées (vérifier l'affichage console également). Fixer les valeurs suivantes pour iOp1 et iOp2:

- iOp1 = 0x7FFFFFFF - iOp2 = 0x0000001

Faites une exécution pas à pas du programme et noter la valeur du résultat de l'addition (iResult). Expliquer le problème. Proposer une solution à ce problème. Quel Flag du registre d'état du processeur permet de détecter ce problème (cf. Annexe 2)?

ATTENTION : par défaut le compilateur ARM génère une addition non signée ADD (ce qui est un bug d’ailleurs…) lorsque l’on écrit le code suivant : int a,b,c ; a = b + c ; Pour forcer le compilateur à générer une addition signée (et donc lever un flag d’overflow), il suffit d’écrire le code comme cela : int a = b + c ;

8) Visualiser le contenu de la mémoire programme (cf. Annexe 1)

a) Déterminer l'adresse de votre fonction main.

Depuis l'outil de debug Keil et en vous servant de la fenêtre "Symbols" (Menu View -> Symbols Window), déterminer l'adresse en mémoire programme de la fonction main (celle du type int f()).

Si ce n'est pas déjà le cas, ouvrez la fenêtre permettant de visualiser le code C et son équivalent assembleur (Menu View -> Disassemby Window).

b) Analyse du code désassemblé

En vous aidant de l'Annexe 1, répondre à ces questions: - qu'est-ce qu'un code assembleur? - qu'est-ce qu'un code désassemblé? - qu'est-ce qu'un fichier .lib? - qu'est que la pré-compilation?

Dans le code C, mettre un point d'arrêt sur la première instruction du main (pc.printf).

Lancer l'exécution du programme et vérifier l'adresse à laquelle le programme s'est arrêté (regardez le code désassemblé). Comme vous pouvez le constater le breakpoint est également visible au niveau du code désassemblé.

9) Analyse du code assembleur (cf. Annexe 2)

Dans le code C, mettre un point d'arrêt sur la première instruction de la boucle do…while (l'instruction d'addition).

a) Analyse du code assembleur pour l'addition des 2 entiers.

Visualisez et essayer de comprendre le code assembleur correspondant à l'instruction d'addition…

INFO2 TP5 page 8

Combien d'instructions assembleurs sont nécessaires pour exécuter l'instruction d'addition écrite en langage C? Quels sont les 3 types d'instructions utilisés?

b) Visualiser des registres du CPU ARM (cf. Annexe 2)

Si ce n'est pas déjà le cas, ouvrez la fenêtre permettant de visualiser les registres du processeur ARM Cortex-M3 (Menu View -> Registers Window).

Quels registres du processeur ARM sont utilisés pour effectuer cette opération d'addition?

Dans quel registre est rangé le résultat de l'addition (correspondant à la variable iResult)?

c) Analyse du code assembleur pour la structure itérative (do… while).

Dans le code C, mettre un point d'arrêt sur l'instruction while.

Visualisez et essayer de comprendre le code assembleur permettant de réaliser la boucle do…while.

Quel registre du processeur ARM est utilisé pour réaliser cette boucle? Quelles instructions assembleur permettent de tester la valeur de iResult et de revenir au début de la boucle?

Quel registre du processeur ARM contient toujours l'adresse de la prochaine instruction assembleur à exécuter? Visualiser la valeur de ce registre lorsque l'on revient au début de la boucle.

Juste avant le test de boucle, forcer la valeur de iResult à 0 afin de sortir de la boucle do…while. Vérifier que cela marche et que le message "Fin du programme" s'affiche bien sur la console.

10) Appel de fonction

Ecrire une fonction permettant d'additionner 2 entiers passés en paramètre. Voici son prototype: int Addition(int, int); Il s'agit de remplacer dans le code C l'instruction

iResult = iOp1 + iOp2

par

iResult = Addition(iOp1, iOp2);

a) A quelle adresse de la mémoire programme se trouve la fonction Addition? b) Quelle instruction permet de faire un branchement à cette adresse? c) Dans quel registre du processeur est stockée l'adresse de retour de la fonction appelante? d) Quels registres du processeur ARM sont utilisés pour le passage des 2 paramètres à la fonction appelée? e) Quel registre du processeur ARM est utilisé pour retourner le résultat à la fonction appelante? f) Quelle instruction permet, à la fin de la fonction, de retourner dans la fonction appelante (i.e. la fonction main) et continuer l'exécution du programme?

11) Si vous avez le temps, compléter votre programme en écrivant une routine d'interruption permettant d'allumer une led lorsque l'on appuie sur un bouton poussoir.

Répondre aux mêmes questions que la question précédente (en remplaçant la fonction Addition par la routine d'interruption).

INFO2 TP5 page 9

ANNEXE 1 – Chaîne de compilation

Un processeur exécute un code binaire, c'est-à-dire comprenant des 0 et des 1. Mais, il n'est bien sûr pas envisageable de programmer en langage binaire… Les constructeurs de processeurs (Intel, Motorola, ARM, etc.) fournissent un ensemble d'instructions (ou jeu d'instructions, Instruction Set en anglais), appelées instructions assembleurs. Afin d'obtenir un code binaire exécutable sur le processeur cible, le code assembleur doit être "assemblé" puis "linké" (cf. la figure ci-dessous). Le développement de code assembleur est de plus en plus rare (mais attention il est parfois toujours nécessaire) car il a un problème de portabilité. En effet, un code assembleur développé pour un processeur Intel n'est pas compatible avec un processeur ARM par exemple. Le code assembleur est donc trop proche de la machine. Un autre problème de l'assembleur est qu'il est difficile à lire et donc à comprendre (surtout lorsque l'on ne connaît pas le jeu d'instructions du processeur). On a donc développé dès les années 50 des langages de plus haut niveau d’abstraction, comme par exemple les langages Basic, Fortran, Cobol, C/C++ ou Pascal. Le code devient portable et distant du matériel. Dans ces langages, le développeur écrit selon des règles strictes (syntaxe) mais dispose d’instructions et de structures de données plus expressives qu’en assembleur. Ainsi des structures algorithmiques proches du langage humain ont été définies : si-alors-sinon ; tant que-faire ; cas selon; etc. Le code est par conséquent beaucoup plus lisible. Il ne faut cependant pas oublier qu'un processeur ne comprend que du code binaire! Il est donc nécessaire de traduire un langage de haut niveau (C/C++ par exemple) en langage assembleur puis en langage binaire. C'est le rôle de la chaîne de compilation (généralement fournie avec le processeur cible). La figure suivante propose une vue générale d'une chaîne de compilation.

Le compilateur traduit chaque instruction du langage source en une suite plus ou moins complexe d’instructions en langage assembleur (appelée aussi langage machine). Cette opération de traduction est complexe ; les compilateurs sont des programmes sophistiqués (et comportent parfois des bugs!). Les programmes compilés s’exécutent plus rapidement que les programmes interprétés (puisque la traduction est déjà faite).

INFO2 TP5 page 10

L'assembleur traduit un programme assembleur en un code binaire (les .o ou .obj) non relogé. Ceci signifie simplement que ni les données ni le programme (les fonctions typiquement) n'a encore d'adresses définies dans la mémoire du processeur cible (pas de mapping mémoire). Une bibliothèque (le .lib) permet de regrouper un ensemble de fichier objets. La dernière étape est l'édition de lien (Link en anglais). Elle consiste à définir le mapping, c'est-à-dire à affecter des adresses aux différentes sections stockant les données (data) ou le programme (code). A l'issue de cette étape un programme exécutable sur la machine cible est obtenue (généralement un .exe). Il est également possible (en spécifiant l'option pour cela) de générer un fichier donnant des informations sur le mapping mémoire (fichier .map).

INFO2 TP5 page 11

ANNEXE 2 – Le processeur ARM Cortex-M3

Généralités Le processeur ARM Cortex-M3 est un processeur 32 bits conçu pour le marché de l'embarqué. Introduit en 2004, il appartient à la famille des processeurs Cortex-M et est basé sur une architecture ARMv7-M. C'est un processeur de type RISC (Reduced Instruction Set Computer) comportant donc un nombre limité d'instruction. Il comporte 3 étages de pipeline (Fetch, Decode, Execute) et possède une architecture de type Harvard. La figure ci-dessous montre une vue simplifiée de l'architecture du ARM Cortex-M3.

Une documentation complète sur le processeur ARM Cortex-M3 est disponible à cette adresse (à partir de la page 654): http://www.nxp.com/documents/user_manual/UM10360.pdf Jeu d'instructions Le jeu d'instruction est décrit à partir de la page 657 de ce document. Le processeur ARM Cortex-M3 possède seulement une centaine d'instruction assembleur. Toutes les instructions ont une longueur de 32 bits et beaucoup d'entre elles s'exécutent en 1 seul cycle d'horloge. Il est en mesure d'effectuer une multiplication de 2 nombres sur 32 bits en 1 seul cycle d'horloge et possède une unité matérielle pour l'opération de division. L'architecture est basée sur un modèle Load/Store (instruction LDR et STR), ce qui signifie qu'il n'est pas possible d'effectuer des opérations arithmétiques ou logiques directement avec des opérandes stockées en mémoire. Il faut en effet obligatoirement passer via des registres.

- Soit pour charger une donnée depuis la mémoire (opération Load) - Soit pour stocker la valeur d'un registre dans la mémoire (opération Store)

Les registres Pour fonctionner, le processeur ARM Cortex-M3 possède des registres internes de 32 bits (qui peuvent s'utiliser comme 4 registres de 8 bits, ou 2 registres de 16 bits). Nous n'allons pas voir tous les registres internes, mais les plus importants et ceux nécessaires pour ce TP5.

- R0 à R12: les registres à usage général (general purpose registers). Ils sont utilisés pour effectuer des opérations sur des données.

- R13 à R15: les registres à usage spécifiques. o R13 (SP) : utilisé comme pointeur de pile (Stack Pointer) o R14 (LR) : registre de lien (Link Register), il stocke les informations de retour

(principalement l'adresse de retour) quand une fonction, une sous routine ou une exception est appelée.

INFO2 TP5 page 12

o R15 (PC) : le compteur de programme (Program Counter). Il contient toujours l'adresse de la prochaine instruction assembleur à exécuter (i.e. l'instruction qui a été fetchée).

- PSR : le registre d'état du programme (Program Status Register) qui rend compte de l’état du système après l’exécution d'une instruction. Chacun des bits du registre d’état est un indicateur d’état ou Flag (drapeau). Avec l'outil de développement Keil nous pouvons visualiser les bits 27 à 31 du PSR (nous ne nous intéresserons pas aux autres pour le moment), respectivement:

o N (bit 31, Negative Flag) : égal à 1 si le résultat de la dernière opération est négatif ou inférieur à, 0 sinon

o Z (bit 30, Zero Flag) : égal à 1 si le résultat de la dernière opération est nul, 0 sinon o C (bit 29, Carry Flag) : égal à 1 si le résultat de la dernière addition a généré un bit de

retenue (carry), 0 sinon o V (bit 28, Overflow Flag) : égal à 1 si le résultat de la dernière opération a provoqué

un débordement, 0 sinon o Q (bit 27, Sticky Saturation Flag) : égal à 1 si le résultat d'une instruction SSAT ou

USAT a provoqué une saturation, 0 sinon (rarement utilisé). La figure ci-dessous montre les registres qu'il est possible de visualiser depuis l'outil de développement Keil pour le processeur ARM Cortex-M3 (View -> Registers Window).

Registres à usage

général

Registres à usage

spécifique

Registres d'état