Cours-Programmation C pour systèmes embarqués

Transcription

Cours-Programmation C pour systèmes embarqués
Programmation C pour
systèmes embarqués
Sylvain MONTAGNY
[email protected]
Bâtiment chablais, bureau 13
04 79 75 86 86
Retrouver tous les documents de Cours/TP sur le site
www.master-electronique.com
Présentation des cours : Sommaire

Cours : 10.5 h en 7 séances

1ère partie : Rappel sur le langage C (Exercices de base)

2ème partie : La programmation en langage C avancée

3ème partie : Présentation du TP : Réalisation d’un
algorithme de compression de données.
Université de Savoie
2
Présentation TP et examen

TP : 20 h en 5 séances
Le but de ce projet est d'écrire un programme de compression de
fichiers textes, sans perte d'information. On demande également
d'écrire un décompresseur qui devra restaurer le fichier original.
L’algorithme proposé ici est l'algorithme de Huffman.

Une note :


18 points : Examen de TP avec une partie théorique.
2 points proviennent du travail fait en TP
Université de Savoie
3
1ère partie : Rappel sur le langage C
(exercices de base)

Donner l’exécution du code suivant :
#include <stdio.h>
#include <stdlib.h>
int main(void){
unsigned char i;
unsigned char tab[5]={1,2,4,8,16};
for (i=0;i<5;i++) {
printf("Le %d element est %d\n",i+1,tab[i]);
} return EXIT_SUCCESS;
}
Université de Savoie
4
1ère partie : Rappel sur le langage C
(exercices de base)

Donner l’exécution du code suivant :
#include <stdio.h>
int main() {
int i,j;
for(i=0;i<5;i++){
for(j=5-i;j<5;j++){
printf("++");
}
printf("\n");
}
return EXIT_SUCCESS;
}
Université de Savoie
5
1ère partie : Rappel sur le langage C
(exercices de base)

Écrire une fonction C calculant la longueur d'une
chaîne de caractères, donnée en argument. Le
prototype de la fonction est le suivant :
int longueur(char *s)
Université de Savoie
6
1ère partie : Rappel sur le langage C
(exercices de base)

Écrire une fonction C calculant la longueur d'une
chaîne de caractères, donnée en argument.
int longueur(char *s) {
int n = 0;
while (s[n] != '\0') {
n++;
}
return n;
}
Université de Savoie
7
1ère partie : Rappel sur le langage C
(exercices de base)

Soit un texte donné par une chaîne de caractères. Le
but est de compter le nombre d'occurrences de
chaque lettre minuscule.
Question 1 : Réaliser les déclarations suivantes :



Le texte (chaîne de caractère constante en lettre minuscule)
sera déclaré dans un tableau nommé « ch ». Vous afficherez
la chaîne de caractère à l’écran.
Un tableau d'entiers nommé « occ » permet de compter les
occurrences de chaque lettre de l’alphabet. La taille du
tableau est fixée par une constante (nombre de lettre de
l’alphabet).
Un pointeur nommé « p » pour parcourir le texte.
Université de Savoie
8
1ère partie : Rappel sur le langage C
(exercices de base)
#include <stdio.h>
#define NB_LETTRE 26;
void main(void) {
/* déclaration d'une chaîne <=> tableau de caractères. */
char ch[]="ceci est une chaîne de test";
/* déclaration d'un pointeur sur une chaîne de caracteres. */
char *p = ch;
/* déclaration d'un tableau de 26 cases */
int occ[NB_LETTRE];
printf("Chaîne en mémoire : %s\n",ch);
}
Université de Savoie
1ère partie : Rappel sur le langage C
(exercices de base)

Question 2 : Initialiser le tableau d’occurrence à zéro :
int i=0;
for (i=0; i<NB_LETTRE;i++)
occ[i]=0;

Question 3 : Compter les occurrences jusqu’à la fin de la chaîne
de caractère :
while (*p != '\0') {
if ( (*p >= 'a’) && (*p <= 'z’) ) {
occ[*p-'a'] = occ[*p-'a'] + 1;
}
p++;
}

Question 4 : Afficher le contenu du tableau occ en précisant la
lettre : le nombre de a est … le nombre de b est ….
for (i=0; i<nb_lettres; i++)
printf("Nombre de %c : %d\n", 'a'+i,occ[i]);
Université de Savoie
10
2ème partie : La programmation en
langage C avancée
















Lisibilité du code,
les types de variables,
les typedef,
occupation mémoire,
porté des variables,
les opérateurs,
manipulation de registre,
les structures,
les pointeurs,
le main(),
pointeurs,
la pile,
type de fonction,
allocation dynamique,
les options d’optimisations à la compilation,
les erreurs classiques du C…
11
Lisibilité du code C (1)

Exercice :
Réaliser un code qui imprime les N premiers éléments d'un
tableau d’entier A[ ] en insérant un espace entre les éléments et
en commençant une nouvelle ligne après chaque dixième chiffre.
void main(void){
int A[80],N,i;
scanf("%d",&N);
for (i=0; i<N; i=i+1){
printf("%d", A[i]);
if ((i%10) == 9)
printf("\n");
else
printf(" ");
}
}
//Saisie de N
//Boucle N fois
//Affichage contenu tableau
//Test du 10ème chiffre
12
Lisibilité du code C (2)

Règles à respecter :





Mettre des commentaires explicites
Ne pas trop compresser le code
Respecter une homogénéité dans votre façon de coder.
Déclarer des variables explicites
Organiser la mise en page de votre code, ou respecter
celle déjà existante.
Université de Savoie
13
Lisibilité du code C (3)

Voici une deuxième façon de coder, très pratique
mais beaucoup moins lisible.
void main(void){
int A[80],N,i;
scanf("%d",&N);
for (i=0; i<n; i++)
printf("%d%c", a[i],(i%10==9)? ‘\n‘ : ' ');
}
Université de Savoie
14
Lisibilité du code C (4)

Quelques équivalences… parfois à éviter
Op. Fonction
+= Addition et
affectation
-= Soustraction et
affectation
*= Multiplication
et affection
/= Division et
affectation
%= Modulo et
affectation
++ Incrémentation
Exemple
nombre += 5;
Equivalence
nombre=nombre+5
nombre -= 6;
nombre *= 3;
nombre /= 2;
nombre %= 4;
nombre = nombre % 4
nombre++;
nombre = nombre + 1;
-- Décrémentation nombre--;
nombre = nombre - 1;
y = x++
y = ++x
y
x
x
y
=
=
=
=
Université de Savoie
x
x + 1
x + 1
x
15
Lisibilité du code C (5)
#include <stdio.h>
main(t,_,a)char*a;{return!0<t?t<3?main(-79,-13,a+main(-87,1_,main(-86,0,a+1)+a)):1,t<_?main( t+1, _, a ):3,main( -94, 27+t, a )&&t == 2 ?_
<13 ?main ( 2, _+1, "%s %d %d\n" ):9:16:t<0?t<-72?main( _,
t,"@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+
,/#{l,+,/n{n+,/+#n+,/#;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K
w'K:'+}e#';dq#'l
q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/
+#n';d}rw' i;# ){nl]!/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw'
iK{;[{nl]'/w#q#n'wk nw' iwk{KK{nl]!/w{%'l##w#' i;
:{nl]'/*{q#'ld;r'}{nlwb!/*de}'c ;;{nl'{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ')
}+}{rl#'{n' ')# }'+}##(!!/"):t<-50?_==*a
?putchar(31[a]):main(-65,_,a+1):main((*a == '/') + t, _, a + 1
):0<t?main ( 2, 2 , "%s"):*a=='/'||main(0,main(-61,*a, "!ek;dc
i@bK'(q)-[w]*%n+r3#l,{}:\nuwloca-O;m
.vpbks,fxntdCeghiry"),a+1);}
The International Obfuscated C Code Contest >> http://www.ioccc.org/
Les types de variables (1)
char = 1 octet (signed ou unsigned)
 Int, long, double, float dépendent de la cible processeur utilisée.

Afin de connaître la taille (en octet) d’une variable, on
utilise la fonction sizeof() :
printf("int=%d octets", sizeof(int));
Note : Il n’existe pas de type Booléen en C
Université de Savoie
17
Les types de variables (2)

Cas d’une compilation pour processeur 32 bits

Voir le cas d’un compilateur C pour PIC16F ou PIC18F (microchip)

Voir le cas d’un compilateur C pour DSP TMS320 (Texas
Instruments) (page suivante)
Université de Savoie
18
Les types de variables (3)
Université de Savoie
19
Les types de variables (4)

Donner la représentation binaire des nombres suivants :





char a=64;
unsigned char b=64;
char c=128;
unsigned char d=128;
Quel est l’affichage des fonctions suivantes?




printf("%d",a);
printf("%d",c);
printf("%u",a);
printf("%u",c+a);
Note : Une déclaration est signed par défaut généralement.
Note : %d -> entier signed
/
%u -> entier unsigned
Université de Savoie
20
Les types de variables (5)

Conversion explicite :
La conversion de type consiste à transformer un type de valeur en un
autre, en respectant la syntaxe suivante
(<type>) <expression>

Conversion implicite :
Lorsque les deux opérandes sont de type différent, le compilateur
prévoit une conversion implicite suivant l'ordre :
{ char -> int -> long -> float -> double } et { signed ->
unsigned }
Université de Savoie
21
Les types de variables (6)
Décrivez le conversion du type, ainsi que les
affectations du code suivant :
int main(void){
int n, m, l;
double d;
d = 5;
n = (int) 3.4;
n = 1;
m = 5;
d = 2;
l = (int) (m / d);
d = n / m;
d = n / ((double) m);
d = 'A' * 6.0 – m + 0xA2CL;
return 0;
}
22
Portée des variables
Globale / Locale

Les variables locales sont déclarées dans la fonction les utilisant.
les variables globales sont déclarées en début de programme. Le
linker attribue une adresse fixe et définitive à ces dernières
(variable globale) pendant toute la durée du programme.

L'utilisation de variables locales améliore considérablement la
lisibilité et la sécurité des données dans un programme en C.
Une variable locale existe exclusivement pendant l’exécution de
la fonction où est déclarer la variable.
Université de Savoie
23
Classes de stockage et qualificateurs

Classes de stockage





auto : Définition automatique pas utilisée car implicite.
register : Demande au compilateur d’utiliser un registre (plus utile)
static : La variable locale conserve sa valeur
extern : La variable est déclarée dans un autre fichier
Qualificateurs


const : Spécifie une « variable » non modifiable
volatile : Limite les effets de l’optimisation sur la variable
Université de Savoie
24
Classe de stockage et qualificateur
classe auto
Cette classe n’est pas utilisé car elle est implicite pour les
variables locales. En effet, Les variables locales sont par
défaut "automatiques" , crées à l'entrée de la fonction
qui les déclare et détruites à la sortie. Pour cela elles
sont rangées dans la « pile».
void f(void) {
auto int j = 0;
printf("j vaut %d",j);
}
void f(void) {
int j = 0;
printf("j vaut %d",j);
}
void main(void) {
f();
}
Mais jamais utilisée
. . .
25
Classe de stockage et qualificateur
classe static

La classe static permet à une variable locale d’être persistante et donc
de conserver sa valeur pendant les appels successifs de la fonction.
void f(void) {
static int i = 0; /* i ne sera initialisé qu’une fois*/
int j = 0; /* j sera initialisé à chaque fois */;
i++;
j++;
printf("i vaut %d et j vaut %d.\n", i, j);
}
void main(void) {
f();
f();
}

Note : La classe « static » sur une variable globale ou une fonction aura
pour objectif de privatiser l’objet au fichier où elle est déclarée. C’est-àdire qu’elle ne pourra pas être utilisée depuis un autre fichier.
Classe de stockage et qualificateur
classe extern (1)

extern : permet de spécifier que la variable a été déclaré dans un
autre fichier.
/* File : ext.c */
void next(void);
void next1(void);
int a1=1; /* definition of external (non static)*/
void main(void){
a1=2;
printf("a1=%d\n",a1);
next();
next1();
printf("a1=%d\n”,a1);
}
27
Classe de stockage et qualificateur
classe extern (2)
/* File file1.c */
/* File file2.c */
int b1=0;
extern int a1;
void next(void){
char a1;
a1='a';
b1=77;
}
void next1(void){
float b1;
b1=19.2;
a1=13;
}
Si on omet le terme extern, une nouvelle variable est
créer avec une nouvelle allocation mémoire.
28
Classe de stockage et qualificateur
classe register
On peut demander au compilateur de ranger une variable très
utilisée dans un registre, à l’aide de la classe register. Le nombre
de registres étant limité, cette requête ne sera satisfaite que s’il
reste des registres disponibles. Cette technique permettant
d’accélérer les programmes a aujourd’hui perdu tout son intérêt
grâce aux performances des optimiseurs de code intégrés au
compilateur.
Documentation du compilateur MikroC :
Université de Savoie
29
Classe de stockage et qualificateur
classe register

Le C définit des qualificateurs pouvant influer sur une
variable :


const : pour définir une variable dont la valeur ne doit jamais
changer ;
volatile : désigne une variable dont les accès ne doivent pas
être optimiser par le compilateur. Cette variable sera relue
depuis son emplacement d’origine à chaque accès. En effet,
cela est important lorsque d’autre sources (périphérique
matériel, processus, etc…) accède à la variable en même temps
que notre programme.
Une variable peut avoir plusieurs qualificateurs
Université de Savoie
30
Classe de stockage et qualificateur
Qualificateur const
Le qualificateur const indique au compilateur que la valeur de la variable
ne doit pas changer. Il est donc impératif d'assigner une valeur à la
déclaration de la variable, sans quoi toute tentative de modification
ultérieure entraînera une erreur de la part du compilateur :
Étudier les codes suivants :
const int i = 0;
i = 1;
void fonction( const char * pointeur ) {
pointeur[0] = 0;
pointeur = "Nouvelle chaîne de caractères";
}
char * const pointeur = "Salut tout le monde !";
pointeur = "Hello world !";
const char * const pointeur = "Salut tout le monde !";
pointeur = "Hello world !";
pointeur[0] = 0;
Université de Savoie
31
Classe de stockage et qualificateur
Qualificateur volatile
int * pReg = (int *) 0x 1234;
while (* pReg == 0) {
…
}
Le compilateur va optimiser la boucle while en considérant
que la lecture de *pReg n’est jamais modifiée. En réalité,
elle peut être modifiée par :



Un périphérique d’entrée/sortie mappé en mémoire
Une interruption
Une autre tâche
Université de Savoie
32
Évaluation des expressions booléennes (1)

Le C ne possède pas de type booléen dédié. Dans ce langage,
n'importe quelle valeur différente de zéro est considérée vraie,
zéro étant considéré comme faux. Ce qui veut dire que n'importe
quelle expression peut être utilisée à l'intérieur des tests (entier,
réels, pointeurs, tableaux, etc.). Cela peut conduire à des
expressions pas toujours très claires, comme :
int a;
a = une_fonction();
if (a) { /* ... */ }

On préférera :
int a;
a = une_fonction();
if (a != 0) { /* ... */ }

Attention :
int a = 0; b = 2;
if (a = b) { /* Le code qui suit sera toujours exécuté ... */ }
Université de Savoie
33
Évaluation des expressions booléennes (2)
Les opérateurs logiques de comparaisons (&& et ||, similaires
sémantiquement à leur équivalent binaire & et |) ont une
exécution totalement différente.
 Dans le cas du ET logique (&&), si l'opérande gauche s'évalue à
faux (valeur zéro), on sait déjà que le résultat du ET sera faux et
donc ce n'est pas la peine d'évaluer l'opérande droite. De la
même manière si l'opérande gauche d'un OU logique (||) est
évalué à vrai, le résultat sera aussi vrai (valeur !=0) et donc
l'évaluation de l'opérande droite est inutile.

if (z != 0 && a / z < 10)
{
printf("Tout va bien\n");
}
Université de Savoie
34
Les manipulations de bits (1)


Les manipulations de bits sont beaucoup utilisées dans
l’embarqué. Pour contrôler un périphérique matériel,
on retrouve des registres de 8, 16 ou 32 bits qu’il faut
modifier.
Mettre à 1 le bit 4 de a :
unsigned int a = 0x000F; /* 0000 0000 0000 1111 */

Mettre à zéro le bit 3 de a :
unsigned int a = 0x000F; /* 0000 0000 0000 1111 */

Faire une fonction int set_bit(int mot, int nbr) qui
retourne le mot modifié à l’emplacement nbr.
35
Les manipulations de bits (1)


Les manipulations de bits sont beaucoup utilisées dans
l’embarqué. Pour contrôler un périphérique matériel,
on retrouve des registres de 8, 16 ou 32 bits qu’il faut
modifier.
Mettre à 1 le bit 4 de a :
unsigned a = 0x000F; /* 0000 0000 0000 1111 */
unsigned b = 0x0010; /* 0000 0000 0001 0000 */
unsigned c = a | b; /* 0000 0000 0001 1111 soit 0x001F */

Mettre à zéro le bit 3 de a :
unsigned a = 0x000F; /* 0000 0000 0000 1111 */
unsigned b = 0xFFF7; /* 1111 1111 1111 0111 */
unsigned c = a & b; /* 0000 0000 0000 0111 soit 0x0007 */
Université de Savoie
36
Les manipulations de bits (2)

Tester si le bit 2 de a est à 1 :
unsigned a = 0x000F; /* 0000 0000 0000 1111 */

Tester si le bit 3 de a est à 1 et si le bit 15 est à 0 :
unsigned a = 0x000F; /* 0000 0000 0000 1111 */
37
Les manipulations de bits (2)

Tester si le bit 2 de a est à 1 :
unsigned a = 0x000F; /* 0000 0000 0000 1111 */
if (a & (1 << 2)) {
printf("bit 2 = 1");
}
else {
printf("bit 2 = 0");
}

Tester si le bit 3 de a est à 1 et si le bit 15 est à 0 :
unsigned a = 0x000F; /* 0000 0000 0000 1111 */
if ( a & (1 << 3)!=0
&&
a&(1<<15)==0 ) {
printf("bit 2 = 1 et bit 15=0");
}
else {
printf("bit 2 = 1 et bit 15=0 n’est pas vérifié");
}
38
Le main()

Le main() est le point d’entrée d’une application. Dans
un système embarquée sans système d’exploitation, le
point d’entrée du programme sera précisé dans la
phase d’édition de liens par l’initialisation du vecteur
d’interruption nommé RESET.
Université de Savoie
39
A quoi sert un pointeur ?
Université de Savoie
40
Les tableaux (1)

Un tableau est un regroupement consécutif de
donnée de même type et de taille fixe.
// Tableau à 1 dimension
// Tableau à 2 dimensions
void main(void){
void main(void){
int tableau[4];
int i;
for(i=0;i<4;i++){
tableau[i]=0;
int tableau[4][3];
int i,j;
for(i=0;i<4;i++){
for(j=0;j<3;j++){
tableau[i][j]=0;
}
}
}
}
}
Université de Savoie
41
Les tableaux (2)
tableau[0]
tableau[1]
tableau[2]
tableau[3]
tableau[0][0]
tableau[0][1]
tableau[0][2]
tableau[1][0]
tableau[1][1]
tableau[1][2]
tableau[2][0]
tableau[2][1]
tableau[2][2]
tableau[3][0]
tableau[3][1]
tableau[3][2]
Université de Savoie
42
Les tableaux (3)


Passage de tableaux en paramètre des fonctions.
Un tableau n’est jamais passé en paramètre, c’est son
adresse qui est fournie.
Université de Savoie
43
Organisation logicielle (1)
0xFFFFFFFF
stack
SP (Stack Pointer)
address space
heap
(dynamically allocated)
data segment
Data : (static and global)
0x00000000
code segment
(=program)
Université de Savoie
PC (Program Counter)
44
Organisation logicielle (2)

Code segment : Emplacement ou se trouve le code compilé (programme)

Data segment :
 Data : contient toutes les variables « static » et « globales »
(initialisées ou non)
 Heap : Le tas est géré dynamiquement. C’est une zone de donnée qui
grossi à la réservation de zone mémoire (malloc) et qui se réduit lors
de la libération (free).

Stack : C’est une zone de stockage de type LIFO. Elle contient les
adresses de retour des fonctions et les variables locales.
Université de Savoie
45
Organisation logicielle (3)
Imposer une adresse physique pour une variable ou une
constante
 Les registres des microcontrôleurs sont à des adresses imposées
par le concepteur du µC, or le linker d'un compilateur C a le
contrôle total des adresses, il choisit où sont rangées variables et
constantes. On peut aussi imposer une adresse à une variable.
Attention, il faut être certain que vous soyez le seul à utiliser
cette adresse pour ne pas créer d’erreur de segmentation.
char c;
void main (void){
*(unsigned char *)0x80 = 0xAA;
c= *(unsigned char *)0x80;
}
46
Organisation logicielle (4)
Directive du linker
Il est possible de préciser à l’éditeur de lien l’endroit exact ou nous
souhaitons que le code, les variables ou les constantes seront
positionnées. Exemple : Pour le linker MikroC, on utilise les directives
suivantes :
Directive absolute : Directive absolute specifies the starting address in RAM for a
variable or a starting address in ROM for a constant..
Directive org : Directive org specifies a starting address of a routine in ROM.
Directive org is appended to the function definition.
Directive orgall : If the user wants to place his routines, constants, etc, above a
specified address in ROM, #pragma orgall directive should be used.
Directive funcorg : You can use the #pragma funcorg directive to specify the
starting address of a routine in ROM using routine name only.
47
Optimisation du code (1)

Dans le contexte de l’embarqué, il est important de connaître les
options de compilation d’optimisation. Il y a en effet un compromis à
trouver entre la taille de l’exécutable produit et le temps d’exécution.
Université de Savoie
48
Optimisation du code (2)

Option -O0 (niveau 0)





Option -O1 (niveau 1)




Allocates variables to registers
Performs loop rotation
Eliminates unused code
Simplifies expressions and statements
Performs all -O0 optimizations, and:
Removes unused assignments
Eliminates local common expressions
Option -O2 (niveau 2) (default optimization level)


Performs all -O1 optimizations, and:
Performs loop optimizations
Université de Savoie
49