PROGRAMMATION DES
MICROCONTROLEURS EN C
TRUCS ET ASTUCES |
 |
Télécharger la version pdf : trucs
et astuces.pdf
Imposer une adresse physique pour une variable
Créer une variables de plus de 256 octets
Gérer les bits
Attendre qu'un bit change d'état
Accéder à un bit par des macros
Tableaux ou
Pointeurs - optimiser la gestion de la mémoire
Variables locales ou globales
A suivre.....
Imposer une adresse physique pour une variable
Les registres des microcontrôleurs sont à des adresses
imposées par le concepteur or le linker d'un compilateur C a le contrôle total des
adresses il choisit ou sont rangées variables et constantes., Définir une
adresse physique permet par exemple de définir comme une variable l'adresse d'un
registre (adresse imposée par le micro contrôleur)
exemple, on veut associer au mot mamem la mémoire RAM à
l'adresse 0x80.
Ceci est un nombre :
0x80
Un CAST transforme ce nombre en un pointeur sur un octet
(unsigned char *) 0x80
Ceci représente le contenu de ce pointeur
*(unsigned char *)0x80
Afin d'éviter cette écriture lourde, on crée une équivalence :
#define mamem *(unsigned char *)0x80
#define mamem *(unsigned char *)0x80
char c;
void main (void)
{
mamem = 0xAA; // on met 0XAA dans la mémoire 0x80
c=mamem; // c= 0xAA
while(1);
}
Créer une variables de plus de 256 octets
C18 est configuré pour optimiser le temps d'accès aux
données, il travaille par défaut sur les bank de 256 octets.
La déclaration d'une variable de plus de 256 octets (un tableau par exemple
déclanche une erreur de link :
MPLINK 3.90.01, Linker Copyright (c) 2005 Microchip
Technology Inc.
Error - section '.udata_varlarges.o' can not fit the section.
Section '.udata_varlarges.o' length=0x0000012c Errors : 1
On peut cependant déclarer un linker des espaces mémoires
supérieurs. Ils s'en accommode, modifie automatiquement les noms des banks lors
des accès. Le temps d'accès est seulement plus long
// Variables de plus de 256 octets
unsigned char c[300]; // c ne rentre pas dans une bank de 256 octest !!!
void main (void)
{
while(1);
}
Pour cela il faut modifier la config du linker, exemple : p18f452.lkr comme ceci
--------------------------------------------------------------------------------------
// Sample linker command file for 18F452i used with MPLAB ICD 2
// $Id: 18f452i.lkr,v 1.2 2002/07/29 19:09:08 sealep Exp $
LIBPATH .
FILES c018i.o
FILES clib.lib
FILES p18f452.lib
CODEPAGE NAME=vectors START=0x0 END=0x29 PROTECTED
CODEPAGE NAME=page START=0x2A END=0x7DBF
CODEPAGE NAME=debug START=0x7DC0 END=0X7FFF PROTECTED
CODEPAGE NAME=idlocs START=0x200000 END=0x200007 PROTECTED
CODEPAGE NAME=config START=0x300000 END=0x30000D PROTECTED
CODEPAGE NAME=devid START=0x3FFFFE END=0x3FFFFF PROTECTED
CODEPAGE NAME=eedata START=0xF00000 END=0xF000FF PROTECTED
ACCESSBANK NAME=accessram START=0x0 END=0x7F
DATABANK NAME=gpr0 START=0x80 END=0xFF
DATABANK NAME=grossebank START=0x100 END=0x3FF // GROSSE BANK DE
768 octets (on peut mettre plus)
DATABANK NAME=gpr4 START=0x400 END=0x4FF
DATABANK NAME=gpr5 START=0x500 END=0x5F3
DATABANK NAME=dbgspr START=0x5F4 END=0x5FF PROTECTED
ACCESSBANK NAME=accesssfr START=0xF80 END=0xFFF PROTECTED
SECTION NAME=CONFIG ROM=config
STACK SIZE=0x100 RAM=gpr4
-------------------------------------------------
grossebank représente maintenant un espace de 768 octets, la directive #pragma
udata permet de forcer le linker à utiliser grossebank (cela est
facultatif, MPLINK recherche automatiquement une bank adaptée et ici il n'y en a
qu'une) il n'y a plus d'erreur de link.
// Variables de plus de 256 octets
#pragma udata grossebank // facultatif
unsigned char c[300];
#pragma udata
// facultatif
void main (void)
{
while(1);
}
Gérer les bits
Le C prévoit la gestion indépendantes des bits à travers
une structure. Il est souvent pratique de disposer de "drapeaux" indiquant qu'un
processus, doit, s'est ou est en train de se dérouler. Afin d'éviter d'utiliser
la plus petite variable du C, le type char pour déclarer un drapeau mais plutôt
un bit (0 ou 1), il faut déclarer une structure "champ de bits"
Dans l'exemple ci dessous on crée le nouveau type "chbits"
et deux structures de bits : drap1 et drap2 qui seront des octets puisque le
type chbits comporte 8 bits.
On accède ensuite aux éléments d'une structure à l'aide du
point (.). nomstructure.element
struct chbits {
unsigned bit0:1; // on aurait pu appeler ce bit
moteur_on ou led_eteinte etc...
unsigned bit1:1;
unsigned bit2:1;
unsigned bit3:1;
unsigned bit4:1;
unsigned bit5:1;
unsigned bit6:1;
unsigned bit7:1;
}
drap1,drap2; // et ici moteur ou
affichage ...
char c;
void main (void)
{
drap1.bit2=1;
// le bit 2 de drap1 est mis à 1
c=drap2.bit5;
// c prend la valeur du bit 5 de drap2 ( o ou 1)
if (drap1.bit5) drap2.bit4=0; // le bit 4
de drap2 est mis à 0 si le bit 5 de drap1 est à 1
while(1);
}
Attendre qu'un bit change d'état
Pour cela on utilise une structure champs de bit comme ci
dessus ou comme celles décrites dans p18f452.h par exemple (accès aux registres
du PIC par nom)
Le principe consiste à rester dans une boucle tant que le bit concerné est dans
l'état opposé à celui que l'on attend.
Exemple : attendre que le bit 0 du port A passe à 1
while (PORTAbits.RA0==0); // on
reste dans cette boucle tant que RA0=0 et on en sort dès qu'il passe à 1
Exemple : attendre que le bit 0 du port A passe à 0
while (PORTAbits.RA0==1); // on
reste dans cette boucle tant que RA0=1 et on en sort dès qu'il passe à 0
Les ==1 et ==0 sont facultatifs puisque la boucle while
fonctionne en terme de "vrai" ou "faux", on peut écrire :
while (!PORTAbits.RA0); ou
while (PORTAbits.RA0);
Accéder à un bit par des macros
Les #define du C permettent de déclarer des équivalence
avec des valeurs numériques mais également avec des fonctions
#define mamem (*(volatile unsigned char *) 0x10) //
definit mamem à l'adresse 0x10
#define BIT(x) (1<<(x)) //
// equivalence de décalage
#define setbit(p,b) (p)|=BIT(b)
// positionne le bit b de l'octet p à 1
#define clrbit(p,b) (p)&=~BIT(b)
// positionne le bit b de l'octet p à 0
void main (void)
{
mamem |= BIT(3);
// le bit 3 de mamem passe à 1
mamem &= ~BIT(3);
// le bit 3 de mamem passe à 0
mamem ^= BIT(3);
// le bit 3 de mamem bascule
if (mamem & BIT(3)) {};
// un test
setbit(mamem,5);
// le bit 5 de mamem passe à 1
clrbit(mamem,5);
// le bit 5 de mamem passe à 0
while(1);
}
Tableaux ou
Pointeurs
Les faibles espaces mémoires des microcontrôleurs peuvent
parfois être résolus grâce à une meilleur gestion de a RAM et de la ROM La
déclaration d'un tableau réserve la taille mémoire (RAM ou ROM) déclarée. La
déclaration d'un pointeur ne réserve rien.
Exemple :a fonction char *mois(char i); qui retourne un pointeur sur une chaîne
de caractères.
a) écriture avec un tableau
char *mois(char i)
{
const char nomdumois[12 ][10] =
{"janvier","février","mars","avril",....};
return nomdumois[i];
}
Occupation mémoire 12 mois de 10 caractères max = 120
octets, on réserve de la place inutilement, exemple pour le mois de mai le
tableau contient
m |
a |
i |
\0 |
\0 |
\0 |
\0 |
\0 |
\0 |
\0 |
donc 6 octets inutilisés
b) écriture avec un tableau de pointeurs
char *mois(char i)
{
const char *nomdumois[ ] = {
"janvier","février","mars","avril", .....};
return nomdumois[i];
}
nomdumois
ð janvier\0 8 octets
nomdumois +1 ð
février\0 8 octets
nomdumois +2 ð
mars\0 5 octets
nomdumois +3 ð
avril\0 6 octets
nomdumois +4 ð
mai\0 4 octets
....
Occupation mémoire : 8 + 8 + 5 +6 + 4 +....
L'occupation mémoire correspond à la somme des tailles des chaînes, la gestion
mémoire est bien meilleure.
Variables locales ou globales
L'utilisation de variables locales améliore
considérablement la lisibilité et la sécurité des données dans un programme en
C. Les variables locales sont par défaut "dynamiques" donc crées à
l'entrée de la fonction qui les déclare et détruite à la sortie. Pour cela elles
sont rangées dans la "pile" , zone mémoire particulière, destinée
primairement au stockage des données de retour des sous programme.
Il peut arriver que l'on souhaite conserver une variable entre deux appels à une
fonction, il faut alors la déclarer "static", elle sera rangée dans la RAM à une
adresse fixe comme une variable globale, mais ne sera "visible" que dans
la fonction qui l'a déclaré.
L'inconvénient des variables globales "dynamiques" est leur temps d'accès .
Le principal avantage est l'optimisation de l'utilisation de
l'espace mémoire RAM souvent petit sur les microcontrôleurs.
Voici trois exemples du même programme qui met en évidence la nécessité d'un
compromis "vitesse d'exécution" "taille du code" "sécurité des données"
Cadre de gauche , le source en C, cadre de gauche, l'assembleur généré par le
compilateur (C18)
Addition sur des variables globales - Un
programmeur en assembleur aurait produit le même code
char a;//3 globales
char b;
char c;
void main (void)
{
a=0;
b=2;
c=3;
a=b+c;
while(1);
} |
void main
(void)
{
a=0;
0000E2 0100 MOVLB 0
0000E4 6B8A CLRF 0x8a, BANKED ; a a été placé
en 0x8a par le linker
b=2;
0000E6 0E02 MOVLW 0x2
0000E8 6F8B MOVWF 0x8b, BANKED ; b a été placé en
0x8b
c=3;
0000EA 0E03 MOVLW 0x3
0000EC 6F8C MOVWF 0x8c, BANKED ; c a tét placé en
0x8c
a=b+c;
0000EE 518B MOVF 0x8b, W, BANKED ; b dans w
0000F0 258C ADDWF 0x8c, W, BANKED ; b+c dans w
0000F2 6F8A MOVWF 0x8a, BANKED ; w dans a
while(1);
0000F4 D7FF BRA 0xf4
} |
Addition sur des variables locales dynamiques ,
cela devient beaucoup plus compliqué en raison des accès par pointeurs dans la
pile
void main (void)
{
char a=0;
char b=2;
char c=3;
a=b+c;
while(1);
} |
void main (void)
0000CA CFD9 MOVFF 0xfd9, 0xfe6
0000CC FFE6 NOP
0000CE CFE1 MOVFF 0xfe1, 0xfd9
0000D0 FFD9 NOP
0000D2 0E03 MOVLW 0x3
0000D4 26E1 ADDWF 0xfe1, F, ACCESS
{ char a=0;
0000D6 6ADF CLRF 0xfdf, ACCESS ; a est dans
0xfdf
char b=2;
0000D8 52DE MOVF 0xfde, F, ACCESS
0000DA 0E02 MOVLW 0x2
0000DC 6EDD MOVWF 0xfdd, ACCESS ; b dans 0xfdd
char c=3;
0000DE 0E03 MOVLW 0x3
0000E0 6EF3 MOVWF 0xff3, ACCESS
0000E2 0E02 MOVLW 0x2
0000E4 CFF3 MOVFF 0xff3, 0xfdb ; c dans 0xfdb
0000E6 FFDB NOP
a=b+c;
0000E8 CFDB MOVFF 0xfdb, 0xfe6 ; c dans fe6
0000EA FFE6 NOP
0000EC 0E01 MOVLW 0x1
0000EE 50DB MOVF 0xfdb, W, ACCESS ; c dans w
0000F0 52E5 MOVF 0xfe5, F, ACCESS
0000F2 24E7 ADDWF 0xfe7, W, ACCESS ; w+ ? dans s w
0000F4 6EDF MOVWF 0xfdf, ACCESS ; w dans a
while(1);
0000F6 D7FF BRA 0xf6 |
Addition sur des variables locales statiques on
retrouve le même code assembleur que pour les variables globales
void main (void)
{
static char a;
static char b;
static char c;
a=0;
b=2;
c=3;
a=b+c;
while(1);
}
|
void main (void)
{
static char a; // trois variables locales statiques
static char b;
static char c;
a=0;
0000E2 0100 MOVLB 0
0000E4 6B8A CLRF 0x8a, BANKED ; 0
dans a
b=2;
0000E6 0E02 MOVLW 0x2
0000E8 6F8B MOVWF 0x8b, BANKED ; 2 dans
b
c=3;
0000EA 0E03 MOVLW 0x3
0000EC 6F8C MOVWF 0x8c, BANKED ; 3 dans
c
a=b+c;
0000EE 518B MOVF 0x8b, W, BANKED ; b dans w
0000F0 258C ADDWF 0x8c, W, BANKED ; w+c dans w
0000F2 6F8A MOVWF 0x8a, BANKED ; w dans
a
while(1);
0000F4 D7FF BRA 0xf4
} |
Les variables locales statiques sont gérés comme les
variables globales, elles restent cependant invisibles en dehors de la fonction
qui les déclare.
A suivre ...
Vous connaissez des astuces en C18 ... envoyez les au
webmestre
|