Micro contrôleurs AVR/Le langage C pour AVR

Leçons de niveau 14
Une page de Wikiversité, la communauté pédagogique libre.
Début de la boite de navigation du chapitre
Le langage C pour AVR
Icône de la faculté
Chapitre no 2
Leçon : Micro contrôleurs AVR
Chap. préc. :Introduction
Chap. suiv. :Les PORTs
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Micro contrôleurs AVR : Le langage C pour AVR
Micro contrôleurs AVR/Le langage C pour AVR
 », n'a pu être restituée correctement ci-dessus.

Ce chapitre comporte beaucoup de redondance avec un autre projet Utiliser les PIC 16F et 18F et particulièrement avec le chapitre Introduction au langage C et ses exercices associés.

Arithmétique binaire et expressions en C AVR[modifier | modifier le wikicode]

Pour bien comprendre la signification des expressions, il est essentiel d’avoir 2 notions en tête : la priorité et l'associativité. Nous donnons ci-après un tableau des opérateurs par priorité décroissante :

Catégorie d'opérateurs
Opérateurs
Associativité
fonction, tableau, membre de structure, pointeur sur un membre de structure ( ) [ ] . -> Gauche → Droite
opérateurs unaires - ++ -- ! ~

* & sizeof (type)

Droite ->Gauche
multiplication, division, modulo * / % Gauche → Droite
addition, soustraction + - Gauche → Droite
décalage << >> Gauche → Droite
opérateurs relationnels < <= > >= Gauche → Droite
opérateurs de comparaison == != Gauche → Droite
et binaire & Gauche → Droite
ou exclusif binaire ^ Gauche → Droite
ou binaire ǀ Gauche → Droite
et logique && Gauche → Droite
ou logique ǁ Gauche → Droite
opérateur conditionnel ? : Droite → Gauche
opérateurs d'affectation = += -= *= /= %=

&= ^= |= <<= >>=

Droite → Gauche
opérateur virgule , Gauche → Droite

La priorité des opérateurs va décroissante lorsqu'on se déplace du haut du tableau vers le bas du tableau. Quand les opérateurs ont même priorité, c’est la colonne de droite sur l’associativité qui est utilisée.

Exercice 1[modifier | modifier le wikicode]

Enlever les parenthèses des expressions suivantes lorsqu'elles peuvent être retirées.

a=(25*12)+b;
if ((a>4) &&(b==18)) { }
((a>=6)&&(b<18))||(c!=18)
c=(a=(b+10));

Évaluer toutes ces expressions pour a=6, b=18 et c=24

Quelques types numériques[modifier | modifier le wikicode]

Les types du C AVR sont[1] :

unsigned char a;//8 bits, 0 to 255
signed char b; 	//8 bits, -128 to 127
unsigned int c;	//16 bits, 0 to 65535
signed int d;	//16 bits, -32768 to 32767
long e;		//32 bits, -2147483648 to 2147483647
float f;	 //32 bits


Travail sur les bits individuels en C[modifier | modifier le wikicode]

Les explications de cette section peuvent être reprises sans modification du livre sur les PIC 16F : Travail sur les bits individuels en C. Pour éviter les allers et retours, nous allons reproduire cette section ici.

Lorsqu'on programme en C sur une petite architecture comme le ATMegaXXX on est très souvent amené à faire des tests sur les bits individuels de certains registres. On peut aussi être amené à être plus actif en positionnant certains bits individuels à 1 ou à 0.

Positionner un bit particulier en C[modifier | modifier le wikicode]

Le principe de base est d’utiliser des masques.

Les opérations logiques effectuées sont des opérations bit à bit :

  • & et logique bit à bit en C pour positionner un ou plusieurs 0
  • | ou logique bit à bit en C pour positionner un ou plusieurs 1

Les masques seront exprimés en hexadécimal. Ce n’est pas obligatoire car certains compilateurs offrent la possibilité d'écrire en binaire, mais si l’on veut être portable, il vaut mieux utiliser l'hexadécimal.

Utisation de masques[modifier | modifier le wikicode]

Début d’un principe
Fin du principe

Un schéma de principe sera peut-être plus parlant :

Utiliser des masques pour positionner des bits

Les masques sont donnés en rouge en binaire et en hexadécimal. Les X dans le registre représentent des valeurs que l’on ne connait pas. Remarquez que les X sont restés des X (ce qui signifie qu’ils n'ont pas changé) sauf pour un seul bit.

Début d’un principe
Fin du principe


Utilisation de macros[modifier | modifier le wikicode]

Il existe plusieurs autres méthodes pour positionner les bits (http://www.microchipc.com/HiTechCFAQ/index.php)

1° méthode pour positionner bit à bit :

	#define bit_set(var,bitno) ((var) |= 1 << (bitno))
	#define bit_clr(var,bitno) ((var) &= ~(1 << (bitno)))
	unsigned char x=0b0001;
	bit_set(x,3); //now x=0b1001;
	bit_clr(x,0); //now x=0b1000;*/

2° méthode pour positionner plusieurs bits

	#define bits_on(var,mask) var |= mask
	#define bits_off(var,mask) var &= ~0 ^ mask
	unsigned char x=0b1010;
	bits_on(x,0b0001); 	//now x=0b1011
	bits_off(x,0b0011);	//now x=0b1000 */

Tests de bits en C[modifier | modifier le wikicode]

Utilisation des masques[modifier | modifier le wikicode]

On utilise encore des masques mais en général toujours avec l'opérateur ET.

Test de bit à l'aide d'un masque
Début d’un principe
Fin du principe

Cette valeur prédéfinie est égale au masque lorsqu'on cherche à savoir si c’est un 1 ou à 0 partout dans le cas contraire. Le test de gauche dans la figure ci-dessus peut donc s'écrire :

   // unsigned char Reg;
   if ((Reg & 0x40) == 0x40) { //E.B. vraie si bit B6 à 1
     ...
   }

tandis que le test de droite s'écrit quant à lui

   // unsigned char Reg;
   if ((Reg & 0x08) == 0x00) { //E.B. vraie si bit B3 à 0
     ...
   }

Il n’est pas possible de supprimer les parenthèses dans les deux programmes ci-dessus.

Utilisation d'une macro[modifier | modifier le wikicode]

Les tests d'un bit particulier en C peuvent aussi être réalisés de la manière suivante (http://www.microchipc.com/HiTechCFAQ/index.php)

	x=0b1000; //decimal 8 or hexadecimal 0x8
	if (testbit_on(x,3)) a(); else b(); //function a() gets executed
	if (testbit_on(x,0)) a(); else b(); //function b() gets executed
	if (!testbit_on(x,0)) b(); //function b() gets executed
	#define testbit_on(data,bitno) ((data>>bitno)&0x01)


Utilisation de fonctions prédéfinies du compilateur avr-gcc[modifier | modifier le wikicode]

Nous avons réuni en annexe I un ensemble d'instructions disponibles avec notre compilateur C. Il vous faut bien distinguer la macro _BV de l'instruction sbi. La première peut mettre son résultat dans une variable (ou un Registre), tandis que la deuxième travaille sur un PORT ou un registre en général.

Exercice 2[modifier | modifier le wikicode]

On présente ci-dessous un registre (ou une case mémoire) de 8 bits avec la numérotation des bits associés.

b7
b6
b5
b4
b3
b2
b1
b0
1
0
1
1
0
0
0
1

Le bit b0 est le poids faible et du coup sera toujours dessiné à droite. Les valeurs dans les cases n'ont aucune importance pour la suite de l'exercice.

Si une variable p1 de type signed char (8 bits signés) est déclarée, écrire les expressions en C (utilisant des masques) permettant de :

  • mettre à 1 le bit b2
  • mettre à 1 le bit b3 et b6
  • mettre à 0 le bit b0
  • mettre à 0 le bit b4 et b5
  • inverser le bit b3 (se fait facilement avec un ou exclusif)
  • mettre à 1 le bit b2 et à 0 le bit b0
  • mettre à 1 les bits b0 et b7 et à 0 les bits b3 et b4

Exercice 3[modifier | modifier le wikicode]

Refaire l'exercice 2 en utilisant la macro "_BV" du compilateur C.

Utiliser les registres de l'AVR (ATMegaXXX) en C[modifier | modifier le wikicode]

L'idéal, pour une écriture facile en langage C, serait de pouvoir écrire :

// affectation d'une valeur dans un registre :
PORTB = val;

qui pourrait mettre directement la valeur contenue dans la variable val dans un registre appelé PORTB. Pour cela, il faut que le compilateur C (gcc pour nous) connaisse le mot PORTB. En général, ce sera le cas si vous mettez l'entête

#include <avr/io.h>

en tout début de votre programme. Mais qu'en est-il pour les bits des registres ?

Le registre SREG présenté ci-dessus comporte des bits qui possèdent un nom. Ce n’est pas le seul registre à avoir cette propriété. Mais peut-on en déduire que les bits des registres possèdent tous un nom que le compilateur C connait ? La réponse est oui mais probablement pas à 100% !

Exemple de fichier d'en-tête pour gcc[modifier | modifier le wikicode]

Pour illustrer les questions de la section précédente de manière un peu plus concrète, voici un exemple partiel de fichier d'entête :

 /* Port D */ 
 #define PIND	_SFR_IO8(0x10) 
 #define DDRD	_SFR_IO8(0x11) 
 #define PORTD	_SFR_IO8(0x12) 
 
 /* Port C */ 
 #define PINC	_SFR_IO8(0x13) 
 #define DDRC	_SFR_IO8(0x14) 
 #define PORTC	_SFR_IO8(0x15) 
 
 /* Port B */ 
 #define PINB	_SFR_IO8(0x16) 
 #define DDRB	_SFR_IO8(0x17) 
 #define PORTB	_SFR_IO8(0x18)
 ....
 /* PORTB */ 
 #define PB7	7 
 #define PB6	6 
 #define PB5	5 
 #define PB4	4 
 #define PB3	3 
 #define PB2	2 
 #define PB1	1 
 #define PB0	0
 ...

Essayez de distinguer la définition d'un registre dans la première partie de la définition d'un bit dans la deuxième partie !

Les possibilités pour atteindre un bit particulier[modifier | modifier le wikicode]

Voici à travers un exemple comment accéder à un bit particulier pour le registre TWAR. On commence par présenter le fichier d'entête :

....
/* TWAR */ 
#define TWA6 7 
#define TWA5 6 
#define TWA4 5 
#define TWA3 4 
#define TWA2 3 
#define TWA1 2 
#define TWA0 1 
#define TWGCE	0
...

Voici maintenant une utilisation :

void main( void) {
....
//*** Toujours pour set *****
  TWAR |=(1<<6);
//**** Si on connaît le nom 
  TWAR |=(1<<TWA5);
//*** Toujours pour reset *****
  TWAR &= ~(1<<6);
//**** Si on connaît le nom 
  TWAR &= ~(1<<TWA5);
...

}

Exercice 4[modifier | modifier le wikicode]

Soit le fichier d'entête :

/* TWCR */ 
#define TWINT	7 
#define TWEA	6 
#define TWSTA	5 
#define TWSTO	4 
#define TWWC	3 
#define TWEN	2 
/* bit 1 reserved (TWI_TST?) */ 
#define TWIE	0

1°) Dessiner le registre correspondant

2°) En utilisant les masques du TD précédent, écrire les instructions C permettant :

  • mise à 1 de TWEA
  • mise à 0 de TWWC.
  • tester si TWINT est à 1
  • tester si TWEN est à 0

3°) Refaire le même travail en utilisant les noms des bits directement.

Remarque : il existe une autre manière de faire ces mises à un et mises à 0 qui utilisent une macro « _BV » :

 void main( void) { 
  .... 
  //*** Toujours pour set ***** 
    //TWAR |= (1<<6) | (1<<5); 
  //**** Si on connaît le nom 
    TWAR |= _BV(TWA6) | _BV(TWA5); 
  //*** Toujours pour reset ***** 
    TWAR &= ~_BV(TAW5); 
  ... 
  
  }

Exercice 5[modifier | modifier le wikicode]

Comment peut-on utiliser la macro « _BV » pour mettre à 0 plusieurs bits à la fois ?

Annexe I : Le GNU C[modifier | modifier le wikicode]

Pour compiler manuellement vous lancez :

avr-gcc -g -mmcu=atmega8 -Wall -Os -c hello.c
avr-gcc -g -mmcu=atmega8 -o hello.elf -Wl,-Map,hello.map hello.o
avr-objcopy -R .eeprom -O ihex hello.out hello.hex

Le format ELF est le format donné par tous les compilateurs du GNU. Cependant les programmateurs utilisent plutôt un format Intel (HEX) d'où la dernière ligne.

Retrouver le code source assembleur[modifier | modifier le wikicode]

Si vous voulez connaître le code source assembleur :

avr-objdump -S hello.elf

puisqu' hello.elf a été créé avec la deuxième commande à partir de hello.o

Autre manière :

avr-gcc -g -mmcu=atmega8 -Wall -Os -c hello.c -o hello.s

Assembler un programme[modifier | modifier le wikicode]

avr-as -mmcu=attiny261 --gdwarf2 ../tb1sw.asm
avr-ld -o tb1sw.elf a.out

Retrouver les informations[modifier | modifier le wikicode]

avr-objdump -h -S hello.elf > hello.lss 

Retrouver la taille du code[modifier | modifier le wikicode]

La commande

avr-objcopy -O binary -R .eeprom  hello.elf hello.bin

génère un fichier binaire dont la taille est celle du code.

Fonctions de manipulation de bits[modifier | modifier le wikicode]

Quelques fonctions non standard de la librairie du C pour AVR sont données :

Remarque : Attention, comme indiqué dans le tableau, certaines fonctions/macros sont obsolètes et leur usage non recommandées.

Fonction Définition Exemple
_BV(x) positionne un bit spécifique
char result = _BV(PINA6); // met 64 dans result
void sbi (uint8_t port, uint8_t bit) positionne un bit spécifique à 1
sbi (PORTB, 3); //#define PORTB 0x18
sbi (PORTB, PINA3);
void cbi (uint8_t port, uint8_t bit) positionne un bit spécifique à 0
cbi (PORTB, 3); //#define PORTB 0x18
cbi (PORTB, PINA3);
uint8_t bit_is_set (uint8_t port, uint8_t bit); teste si un bit spécifique est à 1
//#define PINB 0x16
uint8_t result = bit_is_set (PINB, PINB3);
uint8_t bit_is_clear (uint8_t port, uint8_t bit); teste si un bit spécifique est à 0
//#define PINB 0x16
uint8_t result = bit_is_clear (PINB, PINB3);
uint8_t inp (uint8_t port); lit un registre spécifique et retourne le résultat
//#define SREG 0x3F
 uint8_t res = inp (SREG);
uint16_t __inw (uint8_t port); lit un registre spécifique 16 bits et retourne le résultat
//#define TCNT1 0x2C
uint16_t res = __inw (TCNT1);
uint16_t __inw_atomic (uint8_t port); lit un registre spécifique 16 bits et retourne le résultat sans interruption possible
//#define TCNT1 0x2C
 uint16_t res = __inw (TCNT1);
outp (uint8_t val, uint8_t port); sort une valeur spécifique sur un port spécifique
//#define PORTB 0x18
 outp(0xFF, PORTB);
__outw (uint16_t val, uint8_t port); sort une valeur spécifique 16 bits sur un port spécifique
//#define OCR1A 0x2A
 __outw(0xAAAA, OCR1A);
__outw_atomic (uint16_t val, uint8_t port); sort une valeur spécifique 16 bits sur un port spécifique sans interruption
//#define OCR1A 0x0A
 __outw_atomic(0xAAAA, OCR1A);

Références[modifier | modifier le wikicode]