Académique Documents
Professionnel Documents
Culture Documents
H 21 Lier l'architecture et la
programmation d'un
microcontrôleur
TGE
Contenu
1. Résumé de la première session. ................................................................................................................5
1.1. Algorithmie. ....................................................................................................................................5
1.2. Le C. ...............................................................................................................................................6
1.3. Matériel ...........................................................................................................................................8
1.4. Environnement de programmation .................................................................................................8
2. Les Fonctions ...........................................................................................................................................9
2.1. La Déclaration, l'Appel et la Définition ..........................................................................................9
2.1.1. Fonction et type VOID ..........................................................................................................10
2.1.2. Retour de valeur par une fonction .........................................................................................10
2.1.3. Quatre exemples de fonctions : .............................................................................................11
2.1.4. Fonction avec passage de paramètres par valeur: ..................................................................12
2.2. Entête de fonction .........................................................................................................................12
2.3. Analyse d'un problème (le dé). Algorithme et programme avec fonction. ...................................13
2.3.1. Problème: Allumer les DELs pour faire afficher les 6 états d'un Dé. ...................................13
2.3.2. Algorithme complet pour afficher un Dé. .............................................................................14
2.3.3. Et si on y allait avec des fonctions? .......................................................................................14
3. La boucle "for" .......................................................................................................................................15
3.1. Différence entre la boucle ‘for’ et la boucle ‘while’ ....................................................................16
4. Architecture du DS89C450 ....................................................................................................................17
4.1. Schéma Bloc .................................................................................................................................17
4.2. Caractéristiques.............................................................................................................................18
4.3. Registres spéciaux du DS89C450 (Adresses finissant par 0 et 8 Bit adressables) ..................19
4.4. Les Timers / Compteurs (0 et 1) ...................................................................................................20
4.4.1. La Gestion des Timers / Compteurs ......................................................................................20
4.4.2. Les quatre modes des Timers/Compteurs sont : ....................................................................21
4.5. Les registres des Timers décortiqués: ...........................................................................................22
4.5.1. TMOD (89h) : Ce registre permet de configurer les Timers 0 et 1. ......................................22
4.5.2. TCON 0x88 : .........................................................................................................................22
4.5.3. Calcul de la valeur à mettre dans THx et TLx .......................................................................23
4.6. Exercices sur les Timers ...............................................................................................................24
4.7. Résumé des Timers/Compteurs du DS89C450: ...........................................................................25
5. Lecture des interrupteurs: Détection Front et Anti-Rebond ...................................................................26
5.1. La lecture des interrupteurs...........................................................................................................26
5.2. Technique logicielle, déjà vue, pour détecter Front avec Anti-rebond:........................................26
5.3. Techniques matérielles: ................................................................................................................27
5.4. Technique de l'anti-rebond par compteur: ....................................................................................28
6. Les Tableaux ..........................................................................................................................................29
6.1. Généralités: ...................................................................................................................................29
6.2. Initialisation des tableaux .............................................................................................................29
6.3. Tableau à plusieurs dimensions ....................................................................................................30
6.4. Les chaines de caractères ..............................................................................................................31
6.5. Localisation de nos tableaux (variables).......................................................................................32
7. Les claviers .............................................................................................................................................34
7.1. Lecture du clavier de l'ordinateur (PC):........................................................................................34
7.2. Clavier à brancher: ........................................................................................................................35
7.3. Clavier à matrice: ..........................................................................................................................36
7.3.1. Décodé avec des circuits de bases. ........................................................................................36
7.3.2. Décodé avec un port du microcontrôleur. .............................................................................37
7.3.3. Décodé avec un 74C922. .......................................................................................................38
8. La structure conditionnelle "switch" ......................................................................................................41
9. La boucle "do while" ..............................................................................................................................44
10. Variable locale, globale et statique.........................................................................................................45
10.1. Les variables locales et globales ...................................................................................................45
10.2. Variables statiques ........................................................................................................................46
10.3. Variables globale static .................................................................................................................46
11. Les Bus, la mémoire. ..............................................................................................................................47
11.1. Organisation mémoire du Dallas ..................................................................................................47
11.2. Bus d'adresse, de donnée et de contrôle........................................................................................48
11.2.1. Le 74HCT573 (même fonction qu'un LS373): .....................................................................49
11.3. Lecture d'un programme dans une ROM externe: ........................................................................50
11.4. Accès à une donnée dans une RAM externe: ...............................................................................51
11.5. Décodeur d'adresse. ......................................................................................................................52
11.5.1. Décodeur d'adresse avec le 74LS138. ...................................................................................54
11.6. Carte mémoire complète du Kit Dallas. .......................................................................................55
11.6.1. Circuit Décodeur des I/O du Kit Dallas.................................................................................56
11.6.2. Schéma de la partie Décodeur / Mémoire du Kit Dallas. ......................................................57
11.7. Accès à la RAM externe. ..............................................................................................................58
12. Les pointeurs ..........................................................................................................................................59
12.1. Déclaration ....................................................................................................................................59
12.2. Initialisation ..................................................................................................................................59
12.3. Utilisation .....................................................................................................................................59
12.4. Opérations sur les pointeurs..........................................................................................................60
12.5. Les pointeurs et les tableaux .........................................................................................................60
12.6. Quand utiliser les pointeurs? (3 exemples). ..................................................................................61
12.6.1. Utiliser les pointeurs pour permettre à une fonction de modifier plus d'une variable de la
fonction appelante..................................................................................................................61
12.6.2. Utiliser les pointeurs pour passer de "gros" éléments à une fonction....................................61
12.6.3. Utiliser les pointeurs pour accéder à nos circuits externes. ...................................................62
12.6.4. Quelques informations sur l'écran LCD. ...............................................................................63
12.6.5. Exemple : Accès externe et tableau contenant diverses informations. ..................................64
12.6.6. Exercices sur les pointeurs ....................................................................................................65
12.7. La fonction scanf( ) :.....................................................................................................................66
12.7.1. Exemples de scanf (tiré du manuel du compilateur Keil page 303): ....................................67
13. Utilisation de plusieurs fichiers dans un projet. .....................................................................................68
14. Convertisseur. .........................................................................................................................................69
14.1. Le circuit ADC0804 .....................................................................................................................70
14.1.1. Fonctionnement du ADC0804 ...............................................................................................70
14.1.2. Timing du ADC0804 .............................................................................................................72
15. Organigramme. .......................................................................................................................................73
15.1. Organigramme Traditionnel ou Pseudocode?...............................................................................73
15.2. Organigramme Traditionnel .........................................................................................................74
15.2.1. Exemple: ................................................................................................................................75
15.2.2. Raison des organigrammes traditionnels. ..............................................................................77
15.3. Exemples de programmes: ............................................................................................................78
15.4. Organigramme traditionnel: If, then, else, for, while et switch case. ...........................................81
1.1. Algorithmie.
Opérateurs:
Affectation ( = ): uiDelai = UNESECONDE.
Addition ( + ): iDistance = iDistance + 100.
Soustraction ( - ): cEcart = cEcart – 3.
Multiplication ( * ): uiTotal = uiMoyenne * uiQuantite.
Division ( / ): fMoyenne = (float)uiSomme / uiNombre. // Cast controlé.
ucLuminosite = fConvertisseur / 23.745. // Cast automatique ???
Modulo ( % ): uiUnite = uiTime % 10.
uiDizaine = (uiTime % 100) / 10.
uiCentaine = (uiTime % 1000) / 100.
Structures de programmation:
LIRE:
cTouche = getchar( ); // Lire un code Ascii au clavier.
ucInterrupteurs = P1; // Lire un port a 8 bits.
ucBoutonStart = P1_3; // Lire un bit du port.
ÉCRIRE:
printf("%d", iVitesse); // Ecrire une valeur entiere (16 bits) a l'ecran.
printf("%d", (int)ucMode); // Ecrire une valeur entiere (8 bits) a l'ecran.
printf("%bd", ucMode); // Ecrire une valeur entiere (8 bits) a l'ecran.
P1 = ucBargraph; // ECRIRE 8 bits sur un port.
P1_3 = ucLedMarche; // Ecrire un bit sur le port.
Opérateurs:
Affectation ( = ): ucLuminosite = fConvertisseur / 23;
Calculs: +, -, *, /, %
Binaires (Masques):
&: Pour mettre bit à 0. ucPort = ucPort & 0xF7;
&: Pour isoler des bits. ucPort = ucPort & 0x38;
| : Pour mettre des bits à 1. ucPort = ucPort | 0x02;
^ : Pour inverser l'état des bits. ucPort = ucPort ^ 0x01;
Décalage
>>: Droite: ucPort = ucPort >> 2;
<<: Gauche: ucPort = ucPort << 1;
Types de données:
Nombre (Décimal, Binaire, Hexadécimal)
Caractère ('B') (code ASCII)
Chaine de caractères ("Avion")
Multiplexage:
16.6 ms pour cesser le clignotement (temps max pour tout activer tous les affichages).
VCC
1.3. Matériel R1 R2 R3
2k2 2k2 2k2
SW1
P1.4
SW2
P1.5
SW3
P1.6
VCC
P2.4
LED
Orbee
Keil 5.0
Exemple:
Prédéclaration ou prototype de la fonction (avant le main( ) )
char cConvMaj( char cCarac );
Notes: Par défaut, en C, on considère qu'une fonction retourne toujours une valeur (int).
Quand une fonction qui ne retourne rien, nous devrons le spécifier (void).
Une fonction ne peut pas être définie à l'intérieur d'une fonction.
Le nom des arguments donnés dans la prédéclaration (prototype de la fonction) n'est pas
utilisé par le compilateur. Il sert à rendre la lecture du programme plus clair pour le lecteur.
F.B. / D.C. / P.C. 9 Cours 247-236 Programme de TSO
06-12-2019 NotesCours236_FB.docx
2.1.1. Fonction et type VOID
Lorsqu'une fonction ne retourne aucune valeur, nous le spécifions avec le type void.
Ex.: void vCalcul(int iNombre);
Si une fonction n'a pas d'argument nous pouvons également utiliser le type void pour le spécifier.
Ex.: int iCalcul(void); // Ne reçoit rien et retourne un int.
void vTravail(void); // Ces fonctions ne retournent aucune valeur
void vAnalyse(void); // et ne reçoivent pas d'argument.
Le type de la valeur retournée par une fonction est fixé dans le prototype de la fonction.
Il peut être un type de base (char, int ) ou un type abstrait (structure, union, etc (notions plus
avancées que vous verrez dans un autre cours)).
Pour retourner cette valeur nous utilisons le mot réservé "return" suivit du paramètre entre
parenthèse ou non. Normalement le return est placé à la fin de la fonction.
Ex.: return (expression_Retournee);
La prédéclaration peut être répétée plusieurs fois dans le programme (idéalement une seule
fois avant le main( )), elle indique de quelle sorte de fonction il s'agit (type de retour, types
des paramètres…).
La définition doit apparaitre une seule fois dans le programme (après le main( )), elle
indique ce que fait la fonction.
L'appel peut se faire autant de fois que désiré.
Quand une fonction est appelée, l'exécution du programme est transférée à la première
instruction de cette fonction.
L'exécution de la fonction se termine après l'instruction return ou après la dernière instruction
du bloc de la fonction.
Quand la fonction appelée est terminé, l'exécution dans la fonction appelante, reprend à
l'endroit de l'expression où l'appel avait été fait. La valeur retournée par la fonction peut être
alors utilisée comme opérande dans cette expression.
void vMessage(void)
{
printf("Bonjour le monde\n");
}
Dans ce cours nous utiliserons les fonctions avec passage de paramètre pas valeur. La valeur qui
est transmise à la fonction est une copie de la valeur de base, ainsi il est impossible pour la
fonction qui reçoit le paramètre de modifier la valeur de base. Exemple :
void main(void)
{
UC ucX = 1;
vNeModifiePas(ucX); // Appel de la fonction avec une image de ucX.
printf("%u", ucX); // Affiche : 1.
}
Note: Ici on a 2 variables ucX, mais comme elles sont locales et même si elles ont le même nom,
elles sont vues comme deux variables différentes.
Pour le moment c'est la seul manière que nous allons utiliser les fonctions et pour qu'une fonction
appelée puisse transmettre une valeur à la fonction appelante, nous allons utiliser l'instruction
"return" ce qui permet de retourner une valeur, mais une seule. Ainsi les fonctions par passage de
paramètre peuvent prendre plusieurs paramètres mais ils ne peuvent modifier qu'une seule valeur
de la fonction qui l'a appelé.
Notes: Une fonction possède ses propres variables locales. Elle a accès aux variables globales
mais n'a pas accès aux variables locales des autres fonctions, pas même celles du "main"
Une fonction ne peut retourner qu'une seul valeur d’où l'utilité des pointeurs. (Vues plus
tard)
2.3.1. Problème: Allumer les DELs pour faire afficher les 6 états d'un Dé.
DEBUT
De = 0x01
TANT QUE(VRAI)
SI(De == 0x0E) // Fin de sequence?
De = 0x01. // On recommence.
SINON // On decale ou Inc?
SI((De & 0x01) == 0x01) // Bit 0 = 1?
De = De << 1. // On decale.
SINON
De = De + 1. // On incremente.
FIN SI.
FIN SI.
FIN TANT QUE.
FIN
DEBUT CalculNext(De)
RETOURNE(De).
FIN CalculNext(De).
Description: Instruction est répétée tant que la valeur d'Expression2 est non nulle (VRAI).
Avant la première itération, Expression1 est exécutée. En général elle sert à initialiser
les variables de la boucle.
Après chaque itération de la boucle, Expression3 est exécutée. En général elle sert à
incrémenter le compteur de la boucle.
Exemple: for(i = 0; i < 23; i = i + 1)
{
Total = Total + 2;
}
Autres exemples:
#1: int i, iValeur1, iValeur2 = 0, iValeur3 = 0;
for( iValeur1 = 0 ; iValeur1 < 4 ; iValeur1++)
{
iValeur2 = iValeur2 + 5;
iValeur3 = iValeur3 + 1;
}
Core
Timers
Clock Internal Internal
Part MCU Speed Data 8-16bit VSUPPLY
Type Flash SRAM USART Features
Number Core (MHz) Proces (V)
(KB) (KB)
max
DS89C430 16 5V Tolerant I/O
256-Byte Internal
Scratchpad
4.5
General 8051 Data Pointer
33 8-bit 1 2 3 to
DS89C450 Purpose (CISC) 64 inc/decrement
5.5
MNL 33 Watchdog External
ENG 25 Interrupts
Reduced EMI Mode
80C52 Compatible
o 8051 Pin and Instruction Set
Compatible
o Four Bidirectional, 8-Bit I/O Ports
o Three 16-Bit Timer Counters
o 256 Bytes Scratchpad RAM
Power-Management Mode
o Programmable Clock Divider
o Automatic Hardware and
Software Exit
ROMSIZE Feature
o Selects Internal Program Memory
Size from 0 to 64kB
o Allows Access to Entire External
Memory Map
o Dynamically Adjustable by
Software
Peripheral Features
o Two Full-Duplex Serial Ports
o Programmable Watchdog Timer
o 13 Interrupt Sources (Six
External)
o Five Levels of Interrupt Priority
o Power-Fail Reset
o Early Warning Power-Fail
Interrupt
o Electromagnetic Interference
(EMI) Reduction
Mode 1 : 16 bits
C'est le mode le plus utilisé. Avec 16 bits, on a une période de comptage de 65536 cycles. Ce mode
permet l’utilisation du Timer (mesure d’un temps). Ce mode permet la mesure du temps pendant
lequel un signal extérieur est au niveau haut. Permet de générer des évènements (OverFlow) à
intervalle régulier. En mode Compteur, permet de calculer le nombre de transitions (10) sur une
entrée (T0: P3_4 ou T1: P3_5).
Osc/12 C/T = 0
THx TLx Interrupt
8 bits 8 bits TFx
Tx pin C/T = 1 Contrôle
TRx
GATEx
INTx pin
GATEx THx
8 bits
INTx pin
Mode 3 : Split-Timer
Dans ce mode les deux registres TL0 et TH0 du Timer 0 fonctionnent comme deux Timers 8 bits
indépendants. TL0 utilisent tous les bits de contrôle de Timer0 et active TF0 quand il déborde. Pour
sa part, TH0 utilisent les bits de contrôle de Timer1 (C/T1 et TR1) et active TF1 quand il déborde. En
conséquence, le Timer1 ne peut plus fonctionner en mode compteur, mais il peut fonctionner en mode
Timer pour les modes 0, 1 ou 2 sans action sur son flag ou sur l’interruption. Il peut aussi continuer à
fonctionner pour le baud rate du UART.
Dans ce cours nous utiliserons plus spécifiquement les modes 1 (16 bits) et 2 (8 bits auto-reload)
Timer 1 Timer 0
GATE C/T M1 M0 GATE C/T M1 M0
IE1: Flag qui indique qu'une interruption Externe 1 (/INT1) est active
IT1: Sélection du type de signal sur /INT1: Front descendant/Niveau 0
IE0: Flag qui indique qu'une interruption Externe 0 (/INT0) est active
IT0: Sélection du type de signal sur /INT0: Front descendant/Niveau 0
Pour calculer la valeur de départ à mettre dans le Timer pour avoir un délai précis nous utilisons la
formule suivante:
Avec un compteur 16 bits, on met dans THx:TLx = (65536 – Nombre de comptes) en Hexa.
Donc,
Si on cherche la valeur à mette dans le Timer :
Si on cherche le Délai :
Si le "Nombre de compte" est plus petit que 256 on peut utiliser un Timer 8 bits
Délai(sec) * Fosc( Hz )
Timer(TLx) 256
12
Si on cherche le Délai :
Note : Pour le Timer 8 bits, si on est en mode 2 (8 bits Autoreload) on met aussi la valeur
dans THx.
Exercice 1:
On désire faire un délai de 5 ms avec le Timer 0 et un cristal de 11.0592 MHz calculez les valeurs à
mettre dans TH0 et TL0 ?
Exercice 2:
Utilisation du Timer0 en mode 1. Complétez les commentaires et évaluez le délai du Timer0 ainsi
que la fréquence des pulse sur la pin P3_5 ? (délai = _____ ms, Fréquence = __________ Hz)
#include "DeclarationGeneral.h" //
//
void vInitT0(void); // Predeclaration de la fonction vInitT0
//
void main (void) //
{ //
vInitT0(); // Initialise le ________ en mode ___ ___ Bits
//
while (1) // Faire sans fin
{ //
if(TF0 == 1) // Debordement du __________ ( ms)?
{ //
TL0 = 0x00; //
TH0 = 0xA6; // Remet le d________ a 2_________
TF0 = 0; // Desactive le F_____ de d_________
P3_5 = P3_5 ^ 1; // Fait ____________________________
} //
} // Recommence
} //
void vInitT0(void) // Initialise le Timer___
{ //
TR0 = 0; // Arrete le T___________
TMOD = TMOD & 0xF0; // GATE = __ Timer actif, C/T = __ Mode Timer
TMOD = TMOD | 0x01; // Mode = __ Timer 16 bits.
TL0 = 0x00; // Valeur du Timer = 0xA600 --> Overflow:__________
TH0 = 0xA6; // (65536 - 0xA600) * 12 / 11059200 = _____________
TF0 = 0; // Desactive le F_____ de d_________
TR0 = 1; // Démarre le T__________
} //
Note: Il serait aussi possible de faire un délai plus petit et le compter plusieurs fois pour faire 25ms.
Exercice 4:
Que devez-vous modifier dans le programme précédant pour obtenir une fréquence, sur la pin P3_5,
de 1.6 kHz, avec 50% de cycle de service ?
Exercice 5:
Est-il possible de modifier le programme précédant pour obtenir une fréquence, sur la pin P3_5, de
800 Hz avec 25% de cycle de service ?
Exercice 6:
Que devez-vous modifier dans le programme précédant pour obtenir une fréquence, sur la pin P3_5,
d'environ 2 kHz avec 50% de cycle de service ?
Ici, il faudra détecter un front et même utiliser l'anti-rebond sous peine de voir augmenter
le Compte beaucoup trop vite.
5Volts
0Volt
Bouton pesé Bouton relâché
Sans anti-rebond: Compte serait rendu à 3 serait rendu à 5
5.2. Technique logicielle, déjà vue, pour détecter Front avec Anti-rebond:
SI(ucAncienEtat != ucBoutonMarche) // Changement du bouton Marche?
ucAncienEtat = ucBoutonMarche. // Sauvegarde le nouvel etat.
Delai de 10ms. // Anti-rebond (99% du programme).
SI(ucBoutonMarche == 1) // Front de monte?
// Mettre ici le code a executer si le bouton a ete relache.
SINON // Front de descente.
// Mettre ici le code a executer si le bouton a ete pese.
FIN SI.
FIN SI.
Le gros désavantage de cette technique est que nous restons pris dans le délai de 10ms. Et ce délai
représente environ 99% du temps d'exécution de notre programme. En d'autres mots, on passe 99% de
notre temps à ne rien faire.
Évidemment si on veut faire du contrôle à grande vitesse, cette technique n'est plus valable.
33uF
p737 Trussard R
Charge (0 à 1):
V=E(1-e-temps/RC)
V(RC) = 0.63E (ici: 0.63*5 = 3.2V)
Temps = -RC*ln(1-V/E)
Décharge (1 à 0):
Temps1/2E = RC*ln2 (1/2E = 2.5V)
V(RC) = 0.37E (ici: 0.37*5 = 1.85V)
Donc on cherche RC ≈ 0.01 (10ms).
Technique: Lorsqu'il y a changement de l'interrupteur, il faut trouver une valeur de RC qui nous
permet de rester au même niveau logique durant au moins 10ms.
Passage de 0 à 1. Il faut que le voltage à l'entré reste inférieur à 0.8V durant au moins 10ms.
Avec l'équation: Temps = -RC*ln(1-V/E)
On trouve RC = -Temps / ln(1-V/E) = -0.01Sec / ln(1-0.8/5) = -0.01 / ln(1-0.16)
= -0.01 / ln(0.84) = -0.01 / -0.1743 = 0.057 = RC
Si C = 10uf R = 0.057 / 0.00001 = 5735 ohms. (5600 valeur standard 10%)
Donc, R = 5.6K et C = 10uf.
Passage de 1 à 0. Il faut que le voltage à l'entré reste supérieur à 2V durant au moins 10ms.
Avec l'équation: Temps1/2E = RC*ln2 (Temps1/2E = 0.01sec pour avoir 2.5V)
On trouve RC = 0.01sec / ln2 = 0.01sec/0.693 = 0.0144 = RC
Si C = 10uf R = 0.0144 / 0.00001 = 1442 ohms
Donc, R = 2K et C = 10uf feraient l'affaire.
Pour avoir les mêmes valeurs dans les deux cas, on prend C= 10uf et R = 5.6K.
Avec R = 5.6K le passage du niveau 1 au niveau 0 sera tout simplement plus long.
Action ici
L'avantage de cette technique est que nous ne restons pas pris dans une boucle de délai.
L'inconvénient de la technique c'est le délai qui est difficile à calculer. Ça dépend de la boucle totale
du programme. De plus, le temps d'exécution de la boucle du programme peut varier (changement aux
entrées, affichage de message, etc…).
Pour avoir un délai précis on pourrait utiliser un Timer. Mais les Timers sont très utilisés en contrôle,
il est préférable de les garder pour des applications spécifiques.
Le désavantage des anti-rebonds, c'est que s'ils sont mal ajustés, on peut perdre des informations
pertinentes (un signal qui revient vite pourrait ne pas être lu). Ou avoir des rebonds si trop court.
L'identificateur ucTab est une variable spéciale, dont la valeur est l'adresse de ucTab[0].
Il ne sera donc pas possible de faire figurer ucTab dans le premier membre d'une affectation (Lvalue).
Exemple: ucTab = 10; // Donne l'erreur "C213: left side of asn-op not an lvalue".
// asn-op: assignement operator ( = ).
// lvalue: quelque chose qui peut être à gauche du signe =
// donc qui peut être modifié.
Mais si la dimension est donnée, et qu'elle est supérieure au nombre de valeurs données, les suivantes
seront indéterminées.
UI uiTableau[20]={1,2,3}; // 3 espaces définis les 17 autres indéterminés.
Si la dimension du tableau est inférieure aux nombres de données, il va y avoir une erreur.
int iBad[2] = {1,2,3,4}; // error C242: 'array[]': too many initializers.
void main()
{
UI i;
int iTb[10] = {1,2,3,4,5,6,7,8,9,10}; // Combien d'octets?
vInitPortSerie();
iTb[7] = iTb[4] * 3; // Met (5 * 3) à la place de 8.
printf("%X,%X,%X,%X,%X,%X.\n",iTb[9],iTb[8],iTb[2],iTb[7],iTb[1],iTb[0]);
Résultat : A, 9, 3, F, 2, 1.
Les accolades permettent au programmeur de mieux visualiser les données, ils ne sont pas
obligatoires, mais ils peuvent êtres mal placés. On ne peut pas faire ce qui suit :
unsigned char ucTab[3][2] = {{1,2},{3},{4,5,6}};
unsigned char ucTab[2][3] = {{1,2},{3,4},{5,6}};
Exemple 1:
UC ucChaine1[] = "Salut";
UC ucChaine2[] = "Bonjour";
Note: Un tableau formé d'une chaine de caractères, contient le nombre de caractères de la chaine + 1
car le compilateur ajoute toujours un zéro ('\0') à la fin pour être capable de retrouver la fin
de la chaine lors de la manipulation de cette dernière. Ainsi dans l'exemple précédant
ucChaine1 contient 6 octets et ucChaine2 en contient 8. Si vous voulez travailler sur les chaines
de caractères avec les fonctions du compilateurs, vous devez inclure la librairie <string,h>.
Il est possible de mettre des messages dans des tableaux à plusieurs dimensions pour pouvoir
sélectionner le message avec un indice, mais pour cela vous devez avoir des messages avec le même
nombre de caractère. Dans ce cas vous devez absolument indiquer les dimensions du tableau et
n’oubliez pas d’ajouter un espace pour le '\0', sinon le printf ne saura pas ou se termine la chaine et il
affichera n’importe quoi.
Vous devez soit, placer des espaces à la fin des chaines pour les compléter.
Exemple 2:
UC ucMessage[3][10] = {"Salut ", //
"Bonjour ", //
"Bye Bye !"}; // 9 caracteres + 1 pour le zero = 10.
Si on veut placer nos variables ailleurs, il faut le spécifier lors de la déclaration de la variable.
Exemple: unsigned char data ucToto; // ucToto sera place dans la zone data.
Voici les différentes zones de mémoires accessibles présentement avec votre Kit (interne):
idata
Dans les faits, nous allons spécifier le type de mémoire seulement si nous voulons les placer dans la
mémoire flash (code), ou en RAM externe (xdata).
Exemples:
UI uiTab1[10]; // Localisé en RAM interne du Dallas (data <= 128 octets).
UI xdata uiTab1[10]; // En RAM externe du Dallas ou dans le 1ko interne.
UI code uiTab[3]= {4,7,1}; // En R0M(ou Flash) interne du Dallas (constant).
ucToto = ucToto + 1;
// 050C INC 0x0C // 2 clk, 0x0C: Adresse de ucToto.
ucToto = ucToto + 1;
// 900000 MOV DPTR,#0x0000 // 3 clk, 0x0000: Adr ucToto.
// E0 MOVX A,@DPTR // 3 clk int, 9 clk ext.
// 04 INC A // 1 clk.
// F0 MOVX @DPTR,A // 3 clk int, 9 clk ext.
____ ____
// Total: 10 clk int, 22 clk ext.
1 9
Le tableau contient 100 octets (4*(24+1)). Si on ne met pas "code" lors de la déclaration, nous
utiliserons une très grande partie de la mémoire RAM interne (data: 128 octets).
Les deux fonctions attendent qu'une touche soit pesée avant de nous retourner son code ASCII.
Donc nos programmes "bloquent" si on appelle getchar( ) ou getkey( ). Ceci n'est pas idéal pour faire
du contrôle.
Dans la bibliothèque de fonctions utilisée sur l'ordinateur (PC), il y avait une fonction "kbhit( )" qui
vérifiait si une touche était pesée sans bloquer le programme. Elle retournait VRAI si une touche était
pesée et FAUX si aucune touche n'était pesée. Ainsi on pouvait appeler getchar( ) ou getkey( )
seulement si une touche était pesée. Notre programme ne bloquait donc pas.
Malheureusement, cette fonction n'existe pas pour notre kit, car elle fait accès direct au matériel et
évidemment le matériel de notre kit est différent du matériel du PC.
Il est cependant possible d'écrire une fonction qui fera l'équivalent de kbhit( ). Pour notre kit les
caractères tapés au clavier nous arrivent par le port série. Il s'agit donc de vérifier si le port série a reçu
un caractère. Tout comme le Timer qui a un flag qui nous avertit qu'il y a eu débordement, le port
série a un flag qui nous dit qu'un caractère est arrivé. Ce flag s'appelle RI_0 pour le port série 0. Si ce
flag est actif (1), ça indique qu'un caractère est arrivé et qu'on peut le lire avec getchar( ) ou getkey( ).
#define VRAI 1
#define FAUX 0
UC ucKbHit(void)
{
if(RI_0) return VRAI; // Octet reçu par le port série?
else return FAUX;
}
if(ucKbHit())
{
ucTouche = getkey( );
}
Ainsi, on peut aller lire le clavier seulement si une touche a été pesée ce qui permet à notre
programme de rouler sans bloquer.
Il y a, évidemment, les boutons mais on est vite limité. Le clavier à matrice comportant 10, 12, 16
touches et même plus, sera un outil très intéressant.
Principe:
Un clavier multitouche comporte plusieurs broches. Il est possible avec des circuits électroniques
simples de réduire ce nombre. Ceci nous permettra de les brancher sur un processeur sans trop utiliser
de bits de port.
*
4k7
8 Encodeur
7 10
6 5
4
9
8 14 1
5 3
2
7
6
D
C
6
7
2
3
4 1
13
5
4
B
A
9 4
8
3 12
11
3
2
5
6
LS30
2 1 11
12 Detecteur
LS147
1 de touche
0
VCC
CLAVIER
L'encodeur sort la valeur 0xF si aucune touche n'est pesée. Si on pèse sur une des touches (1 à 9), on
retrouve à la sortie du LS147 la valeur binaire inversée de la touche.
Il nous reste à brancher les 5 fils sur un port du processeur et à vérifier si la sortie du LS30 passe à 1.
Ceci indique qu'une touche est pesée, il nous reste à lire la sortie du LS147 et à l'inverser pour avoir le
numéro de la touche.
Un clavier à matrice comporte plusieurs touches qui sont disposées en lignes et en colonne. Dans
l'exemple suivant le clavier a 16 touches organisées en 4 colonnes et 4 lignes donc 8 broches. Ici
aussi, avec des circuits logiques de base, il est possible de réduire les nombre de broches à brancher au
processeur.
Exemple (clavier 16 touches):
Ici, la sortie du LS13 indique qu'une touche a été pesée (pulse à 1).
Il est possible de remplacer les 4 circuits de l'exemple précédent avec un seul port du Dallas
VCC
A B C D P1_0
E F G H P1_1
I J K L P1_2
M N O P P1_3
P1_4
P1_5
P1_6
P1_7
On va mettre une colonne à 0 et vérifier si une ligne est à 0. Ceci va indiquer qu'une touche a été
pesée. Si aucune touche n'est pesée, on met la colonne suivante à 0 et ainsi de suite pour les 4
colonnes. Le tout pourrait être fait dans une fonction ucLireClavier( ).
Fonctionnement de la fonction:
Si une touche est pesée, la fonction retourne son code (0 à 15 ou encore le code ASCII).
Si aucune touche n'est pesée, la fonction retourne un code spécial (exemple 0xFF).
Si plus d'une touche est pesée, la fonction fait comme si aucune touche n'est pesée.
Si on veut retourner le code ASCII, on peut se servir d'un tableau comme celui-ci en utilisant le
numéro de la touche pour aller chercher un élément dans le tableau.
UC code ucTouche[4][4]={'1','2','3','A', // Definition des touches
'4','5','6','B', // du clavier.
'7','8','9','C',
'*','0','#','D'};
ou
Le 74C922 est un circuit utilisé pour décoder un clavier à matrice pouvant aller jusqu'à 16 touches.
Il contient une horloge interne, des résistances de pull-up internes, un circuit d'anti rebondissement,
une sortie 3-states pouvant être relié à un bus de donnée, une sortie indiquant qu'une touche a été
pesée, un registre interne qui mémorise la dernière touche (même si elle est relâchée).
Connection Diagrams
Pin Assignment for
Dual-In-Line Package
11 17
10 X1 DOA 16
8 X2 DOB 15
7 X3 DOC 14
X4 DOD
1 12
2 Y1 DA
3 Y2
4 Y3
Y4
13
OE
5
6 OSC
KBM
74C922
MSB
Key
Array
Formes d'ondes:
Applications typiques:
___
RD + CS
Une structure switch peut avoir autant de case que vous le souhaitez.
Le cas "default" est optionnel.
Si vous le mettez, les instructions lui correspondant seront exécutées si la variable ne vaut aucune
des valeurs précisées dans les autres cas.
Si vous ne le mettez pas et que la variable est différente des valeurs précisées dans les autres cas,
rien ne se passera.
Il est important de préciser que le switch permet de comparer une variable qu'à des valeurs
ENTIERES! Il est impossible d'utiliser cette structure conditionnelle pour comparer une variable à
un point flottant, par exemple!
switch(uiVarTest)
{
case 5:
printf("uiVarTest vaut 5\n");
break;
case 10:
printf("uiVarTest vaut 10\n");
break;
case 15:
printf("uiVarTest vaut 15\n");
break;
}
Étant donné que la variable uiVarTest vaut 10, et que l'on a un cas qui correspond à cette valeur, on
affichera le message: "uiVarTest vaut 10".
switch(iTest)
{
case 5:
printf("iTest vaut 5\n");
break;
case 10:
printf("iTest vaut 10\n");
break;
}
Ici, iTest vaut 20, mais aucun cas correspondant à cette valeur. On n'affichera donc rien à l'écran.
Pour pallier à tous les cas non traités individuellement, il est possible d'utiliser une option par défaut,
qui s'écrit de la manière suivante :
int iTest = 20;
switch(iTest )
{
case 5:
printf("iTest vaut 5\n");
break;
case 10:
printf("iTest vaut 10\n");
break;
default:
printf("iTest ne vaut ni 5 ni 10\n");
break;
}
Ici encore, aucun cas ne correspond de manière précise à la valeur 20. Puisque l'on a un cas default,
c'est donc celui-ci qui sera exécuté, et on affichera le message: "iTest ne vaut ni 5 ni 10".
switch(iPasBreak)
{
case 5:
printf("iPasBreak vaut 5\n");
// Volontairement, on omet le break !
case 10:
printf("iPasBreak vaut 10\n");
break;
case 15:
printf("iPasBreak vaut 15\n");
break;
}
Il est parfois utile de ne pas mettre l'instruction break. Mais, souvent, en particulier pour les débutants,
c'est un oubli qui entraîne de drôles de résultats à l'exécution du programme ! Soyez prudents.
case 'b':
case 'B':
// Instructions a faire si 'b' ou 'B' est entrée au clavier.
break;
...
}
Comme on peut le voir, la structure de contrôle "switch" sera utile pour la lecture d'un clavier.
Prenons un exemple, qui affiche les valeurs décimales et hexadécimales du code ASCII des touches
que nous entrons au clavier, en utilisant une boucle itérative de la forme do...while.
On quitte la boucle une fois que nous entrons la touche 'ESC'
#define ESC 27
do
{
a = getchar();
printf("%c = %bu = %bX\n", a, a, a);
}while(a != ESC);
Pour obtenir le même résultat avec une boucle while, nous devrions ajouter une instruction avant
d'entrer dans la boucle pour s'assurer d'y entrer.
#define ESC 27
a = 0;
while(a != ESC)
{
a = getchar();
printf("%c = %bu = %bX\n", a, a, a);
}
Ainsi la boucle do...while garantie que les instructions ci trouvant seront exécuté au moins une fois.
Dans l'exemple suivant, nous pouvons constater que l'on entre dans la boucle, alors que la condition
n'est pas vraie. Ce qui nous montre bien que la condition de boucle est testée en fin de boucle, et que
les instructions de la boucle sont toujours exécutées, au moins une fois.
a = 0;
do
{
printf("On est dans la boucle");
}while(a > 10);
Certes, il sera toujours possible d'utiliser le while au lieu du do...while, mais dans le but de simplifier
le code ou d'en améliorer la lisibilité et la compréhension et dans les occasions où ce comportement
correspond à ce que l'on recherche, il sera plus intéressant d'utiliser un do...while au lieu d'un while !
void main(void) //
{ //
UC ucC = 3; // Variable Locale au main( ).
UC ucD = 4; // "
vInitPortSerie() ; //
vAffiche(ucgA, ucgB); // Var Globales passées en paramètres.
vAffiche(ucC, ucD); // Var Locales passées en paramètres.
while(1); //
} //
Résultat : 1, 2. 1, 2. 3, 4. 1, 2.
Note: Utiliser le même nom de variable locale et globale, peut causer des problèmes de "Shadowing" avec certains
compilateurs.
Exemple:
UC ucCompteBoite(void) // Lit Capteur et compte les boites.
{ //
UI uiBoite = 0; // Pour compter les boites.
Malheureusement, cette fonction retournera toujours 1 si une boite est détectée et 0 si aucune boite
n'est détectée. Ce n'est pas le but recherché.
Il est possible, avec l'attribut "static" de dire au compilateur que la variable doit continuer d'exister
même si la fonction se termine. Ceci nous permettra d'accumuler le nombre de boite.
Avec cette déclaration, la variable uiBoite ne sera pas placée sur la pile et ne sera pas éphémère.
Si on rappelle la fonction une deuxième fois la variable uiBoite aura au départ de la fonction sa
dernière valeur (elle sera initialisée à 0, seulement, lors du premier passage dans la fonction).
Si le projet comporte plusieurs fichiers, les autres fichiers ne pourront voir la variable globale static.
Le premier bloc est un bloc de mémoire ROM externe qui pourrait contenir notre programme, à
condition que la broche EA du processeur soit mise à 0 (EA = 0).
On ne peut utiliser qu'un des deux blocs de ROM (Interne EA = 1 ou externe EA = 0).
L'autre bloc, peut contenir jusqu'à 64 kilo octets de mémoire RAM (xdata).
Si la mémoire RAM xdata interne est utilisée (PMR0 = 1 et ) , les
premiers 1024 octets de la RAM externe ne seront pas utilisés.
Dans les faits, cette espace de 64 ko de RAM sera partagé entre de la mémoire RAM et d'autres
circuits électroniques (exemple: Clavier sur un 922, écran LCD, convertisseur, etc…).
Ces circuits électroniques seront vus par le Dallas comme de la mémoire externe (xdata).
Pour accéder à ces deux blocs de mémoires externes (Flash et RAM), nous auront besoin de signaux
électriques pour sélectionner un des circuits et pour échanger les données.
Finalement, pour synchroniser le tout, nous aurons besoin des signaux du bus de contrôle.
U1
2 19
3 D1 Q1 18 Broches:
4 D2 Q2 17
5 D3 Q3 16
6 D4 Q4 15
7 D5 Q5 14
8 D6 Q6 13
9 D7 Q7 12
D8 Q8
11
1 C
OC
74HCT573
Symbole Orcad Data Sheet
Diagramme fonctionnel:
Transparent
Transparent
Opcode Opcode
RAM si RD
CPU si WR
Normalement, le signal CE de la mémoire n'est pas au GND. Ici ça va, car la mémoire occupe
l'ensemble du 64 ko. Si on veut utiliser d'autres circuits, nous devront découper la plage de 64 ko en
plusieurs morceaux (plages).
Exemple: Je veux un ordinateur avec de la RAM de 0x0000 à 0x7FFF, soit 32 ko et un écran LCD
qui sera activé avec toutes les autres adresses (0x8000 à 0xFFFF).
Comme on peut le constater seul A15 reste inchangé pour la RAM (0) et pour l'écran LCD (1).
Nous pourrions donc utiliser A15 pour différencier les deux circuits.
Pour l'écran LCD, plusieurs lignes d'adresses ne sont pas utilisées. Cette méthode de décodage
s'appelle décodage partiel, car pour activer l'écran, on utilise 32768 adresses différentes. On n'utilise
pas toutes les lignes d'adresses disponibles.
Évidemment ce n'est pas très efficace, mais c'est simple à implanter, car on sauve beaucoup de circuits
électroniques.
Note: Seulement deux (2) lignes d'adresses, de la partie basse (A1, A0), se rendent à l'écran LCD car
il y a seulement quatre cases accessibles pour la contrôler. Pour accéder à l'écran il y aura donc
4 adresses de bases: 0x8000, 0x8001, 0x8002 et 0x8003. Les autres adresses seront des images.
Ici la mémoire RAM occupe encore la moitié de la plage de 64ko. Les deux autres circuits pourrait se
partager l'autre moitié (16 ko chaque).
Dans cet exemple on utilisera A15 et A14 pour différencier l'écran LCD et le Clavier.
(A1,A0)
Note: Le circuit 922 ne possède pas de broche CE pour le sélectionner. Nous devrons donc mettre
ensemble le décodeur d'adresse et le signal de contrôle RD pour activer le OE.
0xFFFF
Images Clavier
Donc voici la liste des adresses des différents circuits: 0xC001
0xC000 Clavier (922)
Circuit: Adresses de base: Adresses images: 0xBFFF
RAM
0x0000
Ici on peut voir qu'avec un seul circuit, nous avons 8 blocs de 4096 (4ko) octets.
A15 sert à activer le 74LS138 et les lignes A14, A13 et A12 servent à activer une des sorties du 138.
Si on veut des blocs plus petits, on n'a qu'à utiliser plus de lignes pour activer le 138:
Pour utiliser les autres adresses (0xA000 à 0xFFFF), on n'a qu'à utiliser des portes logiques pour
sélectionner les différentes combinaisons de A14 et A13 afin d'activer le 74LS138.
Dans un cours de la prochaine session, vous verrez en détail, comment concevoir un décodeur
d'adresse complet.
En externe (XDATA) on constate la présence de 32ko de RAM et d’un espace de 32ko pour des
circuits externes.
Pour les circuits externes (I/O) on a un bloc de 32ko. Nous devrons diviser ce bloc pour nous
permettre d'avoir plusieurs circuits différents.
Le décodeur implanté dans le Kit Dallas permet d'utiliser les adresses de 0x0000 à 0x7FFF pour la
RAM (A15 = 0) et les adresses 0x8000 à 0xFFFF (A15 = 1) pour des circuits externes différents.
Le compilateur, par défaut, va mettre les variables à partir de l'adresse 0x0000. C’est l’endroit où se
trouve notre mémoire RAM. Il serait bon de spécifier au compilateur que notre mémoire ne dispose
que de 32ko de RAM.
Dans les options du projet (Bouton de droite sur Target1), nous allons mettre les informations
suivantes:
Il n’est pas vraiment nécessaire de spécifier la grosseur de notre RAM si on gère bien nos données.
Si je déclare une variable UC xdata ucLCD, elle sera placée n'importe où dans la RAM externe.
L'écran du Kit Dallas est, elle, placée à l'adresse 0x8000 (CS0). Je devrais donc forcer le compilateur
à mettre ma variable à cette adresse. Et il faudrait faire ça pour chaque circuit branché sur le Kit.
Ce type de variable va contenir une adresse. On va nommer ces variables des pointeurs.
Un pointeur sera toujours associé à un type de données particulier. Le compilateur doit savoir si le
pointeur pointe sur des variables de 1, 2, 4 octets...
Les pointeurs peuvent pointer sur tous les types de données existantes ou définis par le programmeur.
12.1. Déclaration
TypePointé *NomDuPointeur;
Exemple: int *ipPointeur; // Pointe une zone mémoire remisant des entiers.
12.2. Initialisation
L'initialisation d'un pointeur s'effectue en lui affectant la valeur de l'adresse d'une variable déjà
existante. Pour y arriver nous utilisons l'opérateur d'adressage &.
Ex.: &ucTotal représente l'adresse où est stockée la valeur de la variable du nom de ucTotal.
ATTENTION: Un pointeur qui n'est pas initialisé est l'une des sources d'erreurs les plus difficiles à
régler.
12.3. Utilisation
Pour pouvoir manipuler la valeur stockée à l'adresse contenue par le pointeur nous utilisons l'opérateur
de déréférencement noté * (astérisque).
* ucpPteur représente alors la valeur stockée à l'adresse indiquée par le pointeur ucpPteur.
NOTE: Ne pas initialiser un pointeur avec un nombre numérique. Ex.: ipPointeur = 0x2345;
Nous ne connaissons pas ce qu'il y a à l'adresse 0x2345.
Nous laissons au compilateur le soin de nous allouer une location de mémoire disponible.
Exemple:
void main(void)
{
int i = 2;
int iVal; // Déclaration d'un entier de nom iVal.
int iTableau[5]; // d'un vecteur de 5 entiers.
int *ipPointeur; // d'un pointeur sur des entiers.
12.6.1. Utiliser les pointeurs pour permettre à une fonction de modifier plus d'une
variable de la fonction appelante.
void vFonctionQuiModifieDeuxVariables(UC *ucpVar1, UC *ucpVar2);
void main(void)
{
UC ucVariable1 = 'A';
UC ucVariable2 = 'B';
vFonctionQuiModifieDeuxVariables(&ucVariable1, &ucVariable2);
printf("%c%c",ucVariable1, ucVariable2); // Affiche: OK
while(1);
}
12.6.2. Utiliser les pointeurs pour passer de "gros" éléments à une fonction.
void main(void)
{
int iTab[20] = {0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9}; // 40 octets.
Ce qu'on a vu à date, ce sont des pointeurs logiques. C’est-à-dire qu'ils pointent sur des cases
mémoires sans que l'on connaisse les adresses physiques exactes.
Pour nos circuits externes, l'adresse du circuit doit être connue. Cette adresse nous servira à initialiser
le pointeur externe.
void main(void)
{
vAffCarLCD(0, 0, 'A');
while(1);
}
switch(ucLigne)
{
case 0: ucLigne = LIGNE0 + ucCol; break;
case 1: ucLigne = LIGNE1 + ucCol; break;
case 2: ucLigne = LIGNE2 + ucCol; break;
default: ucLigne = LIGNE3 + ucCol; break;
}
}//Fin vAffCarLCD
#define OFF 0
#define ON 1
#define ETATFAN 0
#define VITFANLUE 1
void main(void)
{
UC ucTableau[2] = {OFF,0}; // ETATFAN, VITFANLUE
while(1)
{
vLireBouton(ucTableau); // ou vLireBouton(&ucTableau[0]);
vLireEtat(ucTableau);
vControlFan(ucTableau);
}
}
////////////////////////////////////////////////////////////////////
void vControlFan(UC *ucpTab)
{
UC xdata *ucpAdrFan = 0x8300; // CS3.
////////////////////////////////////////////////////////////////////
void vLireBouton(UC *ucpTab)
{ // Autre méthode:
if(START) { *(ucpTab + ETATFAN) = ON; } // ucpTab[ETATFAN] = ON;
else { *(ucpTab + ETATFAN) = OFF; } // ucpTab[ETATFAN] = OFF;
}
////////////////////////////////////////////////////////////////////
void vLireEtat(UC *ucpTab)
{
UC xdata *ucpAdrFan = 0x8300; // CS3.
- Le champ type est un caractère qui spécifie si l'entrée est de type nombre, caractère ou chaine de
caractères, comme l'indique la table suivante (les plus communs):
Character Argument Type Input Format
d int * Nombre entier signé.
u unsigned int * Nombre entier non signé.
x unsigned int * Nombre hexadécimal non signé.
f float * Nombre point flottant.
c char * Un simple caractère. (getchar fait mieux la job)
s char * Une chaine de caractères terminés par "\0".
- Le champ «width» est un nombre qui spécifie le maximum de caractère lue sur l'entrée standard.
- Les options b, h, et l doivent immédiatement précéder le type pour spécifier char, short, ou long
pour des entiers de types d, u, et x.
Notes:
Avec Keil le nombre total d'octets qui peuvent être passé à la fonction "scanf" est limité due à
l'espace mémoire restreinte du 8051. Ainsi un maximum de 15 octets peut être passé en model
SMALL ou COMPACT et un maximum de 40 octets peut être passé en model LARGE.
Un caractère qui suit le symbole "%" et qui n'est pas reconnu comme un format spécifié sera traité
comme un caractère ordinaire. Par exemple, "%%" scanf va s'attendre à recevoir le symbole "%"
en entrée.
La fonction "scanf" retourne le nombre d'entrée correctement lues. EOF si une erreur est
rencontrée.
void vTestScanf(void);
void vInitPortSerie(void);
void main(void)
{
vInitPortSerie();
vTestScanf();
while(1);
}
void vTestScanf(void)
{
char a; // Variables signees.
int b;
long c;
unsigned char x; // Variables non signees.
unsigned int y;
unsigned long z;
float f, g; // Variables float.
char buf [10]; // Variables char.
int argsread; // Nombre d'arguments lus.
printf ("\nEnter an unsigned byte, int, and long: "); // Non signees.
argsread = scanf ("%bu %u %lu", &x, &y, &z);
printf("\nX= %bu Y= %u Z= %lu", x, y, z);
printf ("\n%d arguments read\n", argsread);
void vInitPortSerie(void)
{
T2CON = T2CON & 0xCF; // RXClk et TXClk viennent du Timer 1
SCON0 = 0x50; // Port Serie 0: mode 1, 8-bit UART avec reception.
TMOD = TMOD | 0x20; // TMOD: Timer 1, mode 2, 8-bit auto reload.
TH1 = 0xFF; // TH1: reload value for 57600 baud @ 11.059MHz.
PCON = PCON | 0x80; // SMOD_0 = 1 (doubleur de baud).
TR1 = 1; // TR1: Part le Timer 1.
TI_0 = 1; // TI a 1 pour permettre transmission 1er charactere.
} // Fin vInitPortSerie().
Jusqu'à maintenant, nous avons toujours utilisé un seul fichier pour nos programmes. On y retrouvait
l'ensemble des tâches à faire. Cette approche fonctionne bien si on a un projet de petite envergure.
Cependant, lorsque que le problème à résoudre devient plus important, la division des tâches entre les
fonctions devient inefficace. Il faut faire une autre division.
Dans un projet de moyenne et de grande envergure, les tâches sont séparées entre différents fichiers.
Chaque fichier contient un ensemble de fonctions œuvrant sur une partie du problème à résoudre.
Exemple, un projet pourrait contenir un fichier avec les fonctions qui ont traits à la configuration du
microcontrôleur (Ex: InitPortSerie. InitTimer), un autre fichier pourrait s'occuper des fonctions
d'affichages, un troisième aurait les fonctions gérant un circuit de conversion quelconque et
finalement un dernier contiendrait la fonction principal (main( )) et quelques fonctions de gestion de
l'application.
D'autres types de fichiers peuvent aussi être ajoutés au projet. Exemple, les fichiers .LIB contiennent
une librairie de fonctions qui gèrent différentes tâches.
C'est pourquoi il existe des circuits permettant de faire le pont entre le monde extérieur (analogique)
vers le monde des ordinateurs (numérique).
Le premier que nous allons voir est le circuit qui permet de convertir une tension analogique en
signaux numériques pouvant être lus par un ordinateur. Il s'agit du Convertisseur Analogue
Numérique (CAN) ou en anglais, Analog Digital Converter (ADC). La plage de tensions à convertir
est différente d'un circuit à un autre, mais elle est souvent ajustable. Dans notre cas nous convertirons
une tension qui se situe entre 0 et 5 volts.
Le principe de la conversion consiste à donner un numéro à chacune des tensions possibles. Comme la
tension peut prendre une infinité de valeur, les concepteurs de circuit ont limité le nombre d'états
possibles. C'est ce qu'on va appeler la résolution du convertisseur. Dans les faits, il s'agit du nombre
de bits utilisés pour donner un numéro.
Exemple: Avec 3 bits on peut donner 8 numéros différents. Si la tension est 0 volts on donne le
numéro 0 et si la tension est 7 volts on donne le numéro 7. Entre ces deux tensions on donne
les numéros 1 à 6.
Il existe plusieurs types de convertisseur analogique à numérique. Ils diffèrent par la technique
employée (Simple rampe, Double rampe, Approximations successives, Flash, Semi-flash, Sigma-
Delta). Le but du cours n'étant pas de vous décrire l'ensemble de ces techniques, nous allons nous
contenter de décrire un type très répandu et abordable, le convertisseur à approximations successives.
Mode Free-running.
(Utilisé pour le tester au lab)
Branchement à un CPU:
Dallas
Note: Pour attendre la fin de conversion, on aurait pu mettre un délai d'au moins 100us.
*Voir la section 11.4 Accès à une donnée dans une RAM externe,
pour trouver la largeur du signal WR du Dallas.
À date nous avons utilisé le pseudocode pour représenter nos algorithmes. Il existe d'autres
techniques. Une technique répandue est l'organigramme de programmation.
L'organigramme traditionnel: Représentation graphique avec pictogrammes (icônes) reliés par des
traits, pour faciliter la lisibilité du programme. Comme chaque
pictogramme a une fonction bien définie, de simples connaissances
de bases permettent de les réaliser. (Ces connaissances de base
seront décrites plus loin)
Inconvénient: Permet de faire des programmes non structurés, ce qui devient impossible à traduire
dans un langage évolué.
Pseudocode: Représentation des programmes avec du texte seulement. Ainsi le texte décrit
fidèlement ce qui doit être fait (LIRE les entrées, Incrémenter le compteur...).
Comme c'est du texte, et qu'il n'y a pas de lien nous devons écrire de manière très
structurée pour que le tout reste lisible et il faut aussi connaître les structures de base
en programmation.
Avantage : Simple à traduire vers un langage évolué, représente clairement l'idée exprimée, se
fait facilement sur plusieurs niveaux. Très facile à modifier.
Inconvénient : Vue d’ensemble plus difficile à faire, la traduction vers un langage de base
(assembleur) peut être difficile.
Non
Avec ces différents pictogrammes il est ainsi possible de faire l'organigramme traditionnel de vos
programmes (des plus simples aux plus compliqués).
Compteur=0 Oui
Sort la variable
Non Compteur = xx sur le port
Oui
Switch1=1
Non Tourne a Retour
gauche
Oui
Switch2=1
Tourne a
Non droite
Le grand inconvénient des organigrammes traditionnels, est qu’ils permettent de faire des
représentations non structurées et donc impossible à coder en ‘C’.
Pour réaliser un organigramme structuré, chaque partie du programme doit respecter la règle
suivante:
une entrée = une sortie.
Méthode structuré
DEBUT Tourne a
gauche
Initialisation
1 Décale de 1 bit
vers la gauche
Compteur -1 sur la variable
de sortie
Compteur=0 Oui
Sort la variable
Non Compteur = xx
sur le port
Oui
Switch1=1
Non Tourne a Retour
gauche
Oui
Switch2=1
Tourne a
Non droite
DEBUT
Initialisation.
TANT QUE(VRAI)
Compteur = Compteur – 1.
SI(Compteur == 0)
Compteur = XX.
SI(Switch1 == 1)
Faire "Tourne A Gauche".
SINON // Switch1 != 1.
SI(Switch2 == 1)
Faire "Tourne A Droite".
FIN SI.
FIN SI.
FIN SI.
FIN TANT QUE.
FIN.
Avec un organigramme traditionnel, on voit facilement les étapes (décisions et actions) à suivre pour
arriver au résultat escompté.
L’organigramme traditionnel permet de mettre sur papier ce que l’on conçoit dans sa tête. Il permet
aussi de faire visualiser à nos coéquipiers ce que l’on veut faire. S’il est fait de manière systématique,
au début de la conception d’un programme, vous sauverez un temps fou.
Faites toujours un organigramme traditionnel, même pour les programmes les plus simples. Un petit
bout de papier et c’est fait. Avec cette pratique, ce sera plus facile lorsque vous aurez de grands
programmes à réaliser (par contre, ça devient difficile à représenter).
L’organigramme traditionnel peut être fait de manière très générale ou très précise. La première
approche donne une vue d’ensemble, mais la seconde est plus facile à coder.
DEBUT
Y a-t-il Oui
quelqu'un qui t'a
Est-ce que Oui vue
ça t'emmerde
vraiment ? Non
Pauvre toi ! ! !
Peux-tu Oui
Non la réparer ?
Tiens ça
Peux-tu Oui
mort !!!
Non blâmer quelqu'un
d'autre ?
Non
Fais de l'air !!!
Peux-tu Oui
la réparer ?
Non
FIN
Programme qui compte en binaire en + ou - selon l’état de l’entrée P3.3 sans délai.
Ordinogramme : Pseudocode : Code Source : C
Prog 5
DEBUT Prog5 void Prog5(void)
{
Oui
TANT QUE(VRAI) UC ucVar
P3.3 = 1 SI(P3_3 == 1) while(1)
Non VAR = VAR – 1. {
SINON if(P3_3 == 1)
VAR +1 VAR -1
VAR = VAR + 1. ucVar--;
FIN SI. else
P1 = VAR. ucVar++;
Port 1 = VAR FIN TANT QUE. P1 = ucVar;
FIN Prog5. }
}
Oui Oui
Condition Condition vraie
Non Instructions
Non
Faire si non Faire si oui
Do-While
If-Then-Else imbriqué
faire
Si (Condition A) {Instructions}
{Si (Condition B) {Faire si A et B} tant que (Condition est vraie)
Sinon {Faire si A et non B}}
Sinon {Faire si non A}
Instructions
Oui
Condition A
Oui
Condition vraie
Non Oui
Condition B Non
Non
Faire si Faire si A Faire si Switch-Case
non A et non B A et B
switch (paramètre) {
case: 'A'{Faire si 'A'; break;}
case: 'B'{Faire si 'B'; break;}
case: 'C'{Faire si 'C'; break;}
For default: {Faire si rien; break;}
Pour (départ; Condition vraie ; incrément) }
{Instruction}
Si Oui
'A' Faire si 'A'
départ Non
Si Oui
'B' Faire si 'B'
Oui Non
Condition vraie Si Oui
'C' Faire si 'C'
Non Instructions
Faire si rien
incrément