# Micro contrôleurs AVR/Le langage C pour AVR

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

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.
Une expression ne doit comporter ni trop ni trop peu de parenthèses sous peine de devenir illisible. Tout est donc affaire de dosage et c’est cet apprentissage qui est long. Il faut garder à l'esprit le fait que la taille du programme généré ne dépend pas du nombre de parenthèses.

### Exercice 1

Enlever les parenthèses des expressions suivantes lorsqu'elles peuvent être retirées.
Évaluer toutes ces expressions pour a=6, b=18 et c=24
Evaluation :
* a reçoit 318
* l’expression booléenne dans le if est vraie
* l’expression complète est vraie car c est différent de 18
* b+10 = 28 sera affecté à a puis à c

## Quelques types numériques

Les types du C AVR sont :
Nous vous recommandons d'utiliser plutôt les types prédéfinis du compilateur C (du GNU)
Le gros avantage est que les tailles des types sont dans leurs noms :
* uint8_t est sur 8 bits
* uint32_t est sur 32 bits
Ainsi les types utilisés seront indépendants des cibles.

## Travail sur les bits individuels en C

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

Le principe de base est d’utiliser des masques.
On appelle masque une valeur définie qui va servir à l'aide d'un opérateur ou ou un opérateur et à positionner des bits individuels.
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

Comme déjà évoqué, le principe de base est d’utiliser un OU pour positionner à 1 et ET pour positionner à 0.
Un schéma de principe sera peut-être plus parlant :
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.
* Pour positionner un ou plusieurs bits à 1 on écrit un ou plusieurs des 1 aux places concernées et on complète par des 0. Ceci formera le masque binaire qu’il faudra transformer en hexadécimal et utiliser avec un opérateur OU.
* Pour positionner un ou plusieurs bits à 0 on écrit un ou plusieurs des 0 aux places concernées et on complète par des 1. Ceci formera le masque binaire qu’il faudra transformer en hexadécimal et utiliser avec un opérateur ET.

#### Utilisation de macros

Il existe plusieurs autres méthodes pour positionner les bits (http://www.microchipc.com/HiTechCFAQ/index.php)
1° méthode pour positionner bit à bit :
2° méthode pour positionner plusieurs bits

### Tests de bits en C
#### Utilisation des masques

On utilise encore des masques mais en général toujours avec l'opérateur ET.
On utilise et masque avec un 1 en face du bit que l’on veut tester. On construit ensuite une expression booléenne pour tester le résultat en le comparant à une valeur prédéfinie.
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 :
tandis que le test de droite s'écrit quant à lui
Il n’est pas possible de supprimer les parenthèses dans les deux programmes ci-dessus.

#### Utilisation d'une macro

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)

### Utilisation de fonctions prédéfinies du compilateur avr-gcc

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

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

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

## Utiliser les registres de l'AVR (ATMegaXXX) en C

L'idéal, pour une écriture facile en langage C, serait de pouvoir écrire :
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
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

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 :
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

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

### Exercice 4

Soit le fichier d'entête :
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 » :

### Exercice 5

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

## Annexe I : Le GNU C

Pour compiler manuellement vous lancez :
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

Si vous voulez connaître le code source assembleur :
puisqu' hello.elf a été créé avec la deuxième commande à partir de hello.o
Autre manière :

### Retrouver la taille du code

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

### Fonctions de manipulation de bits

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); |