Micro contrôleurs AVR/Arduino

Leçons de niveau 14
Une page de Wikiversité, la communauté pédagogique libre.
Début de la boite de navigation du chapitre
Arduino
Icône de la faculté
Chapitre no 9
Leçon : Micro contrôleurs AVR
Chap. préc. :De la programmation des AVRs aux bootloaders
Chap. suiv. :Gerarduino
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Micro contrôleurs AVR : Arduino
Micro contrôleurs AVR/Arduino
 », n'a pu être restituée correctement ci-dessus.

L' arduino est un nom générique donné à un ensemble de platines ayant une connectique très spécifique. Son but est de pouvoir recevoir des "shields" qui ne sont autres que des périphériques spécifiques. Le nombre de shields est en constante augmentation. Des périphériques relativement complexes sont ainsi proposés :

  • WIFI
  • USB maître
  • Commandes de moteurs pour la robotique

et bien d'autres encore ...

L'environnement de programmation est basé sur un Environnement de développement intégré (IDE dans la suite de ce chapitre) très simple à utiliser. Le langage utilisé est un dérivé du C, en fait du C++ dont on cache certaines parties.

Internet permet de trouver facilement de la documentation sur Arduino. Pour ne pas faire de redondance avec ce genre de documentation, nous allons présenter des problèmes assez techniques dans ce chapitre. Le genre de problème qu’il est difficile de trouver résolu. Nous avons, par exemple, décidé d'ajouter en fin de ce chapitre l’utilisation de platines Arduino avec le compilateur avr-gcc, autrement dit sans l'environnement ARDUINO. Nous aborderons aussi le problème des claviers USB, complexe à cause de nos claviers AZERTY et de nos caractères accentués.

Des platines libres[modifier | modifier le wikicode]

Un exemple de platine Arduino avec ses connecteurs typiques

Les platines sont naturellement vendues mais elles sont toujours proposés aux amateurs (de grandes sensations) de manière complètement libre. Il est donc possible en principe de fabriquer sa propre carte Arduino, mais cela devient de plus en plus difficile avec les évolutions des processeurs. On est passé des boîtiers DIP du processeur AVR ATMega8 aux boîtiers CMS ou SMD (pour Surface-Mount Device) des AVR ATMega 328 (32 broches). Travailler avec ce genre de boîtier est devenu difficile pour l'amateur, aussi éclairé soit-il.

S'il vous est possible de fabriquer des platines même à échelles industrielles, il vous est en revanche interdit de leur donner "Arduino" comme nom. C'est pour cela que l’on a vu fleurir des noms concurrents assez évocateurs : Freeduino, Sanguino, Seeduino, Uduino, Diduino, etc.

Nous vous présentons ci-contre un exemple typique de platine Arduino. Regardez ses connecteurs typiques (entourés de rouges), c’est avec eux que l’on connecte les "shields".

Le langage Arduino[modifier | modifier le wikicode]

Comme nous avons déjà eu l’occasion de le dire, le langage de programmation est basé sur le langage C/C++. Voyons cela un peu plus en détail.

Voici en image comment se présente l’Environnement de développement intégré ci-dessous à droite.

L'IDE pour la programmation de l'Arduino

La référence du langage Arduino peut facilement être trouvée ici.

Langage C et Arduino[modifier | modifier le wikicode]

La compréhension de cette section nécessite une connaissance du langage C. Rappelons qu’il existe une Introduction au langage C dans la Wikiversité. On peut voir la programmation en langage arduino de la manière suivante : un programme en C ayant la structure :

void setup(void); // prototype de la fonction setup()
void loop(void); // prototype de la fonction loop()

int main(){

  setup();
  while(1) {
    loop();
  }
  return 0;
}

Dans ce programme vous n'aurez qu’à écrire la fonction loop() et la fonction setup(). Autrement dit tout ce qui est présenté dans le listing ci-dessus vous est caché mais est ajouté de manière automatique.

On remarquera que setup() est appelé une seule fois alors que loop() est appelé dans une boucle infinie. Rappelons enfin que la traduction du mot anglais "loop" est "boucle".

Nous espérons que vous avez bien remarqué que la seule chose qui manque au programme ci-dessus pour être complet est naturellement :

void setup(void){

 // votre code ici de la fonction setup()

}
void loop(void){ 
 
 // votre code ici de la fonction loop()

}

et donc que c’est la seule chose que vous avez à faire en programmant votre Arduino.

Un exemple de programme[modifier | modifier le wikicode]

L'entrée anglaise de wikipédia sur le sujet propose l'exemple suivant :

#define LED_PIN 13
 
void setup () {
    pinMode (LED_PIN, OUTPUT);     // autorise pin 13 comme sortie 
}
 
void loop () {
    digitalWrite (LED_PIN, HIGH);  // allume la LED
    delay (1000);                  // attend une seconde (1000 milliseconds)
    digitalWrite (LED_PIN, LOW);   // éteint la LED
    delay (1000);                  // attend une seconde
}

Ce programme tout simple fait clignoter une LED reliée à la broche 13 (avec une résistance bien sûr) toutes les secondes.

Exemples de réalisations[modifier | modifier le wikicode]

Nous allons présenter dans cette section un exemple de réalisation et ses programmes associés. Nous espérons que des collègues viendront compléter cette partie pour diversifier les matériels utilisés.

Présentation du matériel utilisé[modifier | modifier le wikicode]

Un shield (bouclier) a été réalisé à l’IUT de Troyes pour l'enseignement des bases de la programmation Arduino. Sa documentation peut être consultée dans ce lien (WIKI interne à GEII Troyes).

Afficher sur deux afficheurs sept segments[modifier | modifier le wikicode]

Les programmes de cette section sont dépendants de la partie matérielle présentée plus haut et surtout ici.

Connexion du shield utilisé aux sept segments[modifier | modifier le wikicode]

Les 2 afficheurs ne peuvent pas être utilisés simultanément. L'état de la sortie mux (arduino port 4 ou PD4) permet de sélectionner l'un ou l'autre. En allumant successivement l'un puis l'autre rapidement, on a l'illusion qu’ils sont tous 2 allumés.

Les segments des afficheurs sont câblés de façon analogue comme décrit ci dessous :

Segment pt g f e d c b a
Arduino Pin 11 9 10 8 7 6 12 13
Port UNO PB3 PB1 PB2 PB0 PD7 PD6 PB4 PB5

Voici sous forme schématique la documentation correspondante :

Documentation du Shield avec carte UNO

Cet ordre pour le brochage peut sembler étrange. Mais elle est liée à des contraintes de routage du shield.

Utiliser une interruption pour allumer une LED[modifier | modifier le wikicode]

Encore une fois ce programme est très dépendant de la partie matérielle présentée plus haut et surtout ici.

Un appui sur le bouton allume la LED tandis qu'un nouvel appui l’éteint :

int pin = 13;
volatile int state = LOW;

void setup()
{
  pinMode(pin, OUTPUT);
// attachInterrupt(0, blink, CHANGE);
  attachInterrupt(0, blink, RISING); 
}

void loop()
{
  digitalWrite(pin, state);
}

void blink()
{
  state = !state;
}

Ce programme est intéressant pour montrer comment utiliser une interruption avec l'Arduino. Cette interruption se déclenche sur un front montant ("RISING") mais peut l'être sur tout changement de niveau ("CHANGE"), ou sur front descendant ("FALLING").

Utiliser "Processing" pour afficher des données graphiques[modifier | modifier le wikicode]

Processing est un environnement de programmation permettant de réaliser du graphisme avec un environnement de type Arduino. La seule différence est qu'au lieu de programmer les célèbres "setup()" et "loop()" on trouvera ici "setup()" et "draw()". L'autre grande différence c’est que le langage utilisé est Java. Voici donc un exemple destiné à réaliser l’affichage sous forme de bar-graphe des valeurs provenant d'un seul capteur.

D'abord le programme Arduino[modifier | modifier le wikicode]

Nous n'utilisons pas de protocole mais envoyons directement la valeur d'un seul capteur en continu.

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  Serial.println(analogRead(A3),DEC);
  delay(500);
}

Ensuite le programme "processing"[modifier | modifier le wikicode]

Le programme processing est un peu plus complexe :

// D'après G.Spanner : "personnalisez vos montages Arduino" Editions Elektor (2013)
// Modifié pour nos besoins
int x_max = 561;
int y_max = 600;
int offset = 60;
int distX = 80;
int distBot = 40;
int colWidth = 20;
int i = 0;
PFont fontA;
int[] sensorValues = new int[6];
import processing.serial.*;

// serial connection
Serial port;
String message = null;
String elements[] = null;
color graphColor = color(25,25,250);
PFont fontGraph;

void draw_grid(){
  background(200);
  for(int x=0;x<=x_max;x+=distX)
  line(x,offset,x,x_max);
  for(int y=0;y<=y_max;y+=50)
    line(0,y+offset,y_max,y+offset);
  textFont(fontA,32);
  fill(graphColor);
  text("AnalogMonitor",x_max/2-100,40);
  textFont(fontA,16);
}

void serialEvent(Serial p){
// message = port.readStringUntil(13);
  message = p.readString();
  if (message != null) {
    try {
// elements = splitTokens(message);
// for (int i=0; i<elements.length && i<6;i++){
// sensorValues[i] = int (elements[i]);
// }
      sensorValues[i] = int (message);
      i++;
      if (i==6) i=0;
    }
    catch (Exception e) {}
  }
}

void setup() {
  size(x_max,y_max);
  noStroke();
  fontGraph =loadFont("ArialMT-12.vlw");
  textFont(fontGraph,12);
  println(Serial.list());
  //port = new Serial(this,"ttyACM0",115200); //doesn't work !!!
  port = new Serial(this, Serial.list()[0], 9600);
  fontA = loadFont("ArialMT-12.vlw"); // need this file in sketch directory
  textFont(fontA,16);
}

void draw(){
  draw_grid();
  for (int i=0;i<6;i++){
    fill(graphColor);
    rect(i*distX+distX-colWidth/2,height-distBot,colWidth,-sensorValues[i]/2);
    text(sensorValues[i],i*distX+distX-colWidth/2,height-distBot+20);
  }
}

Nous reviendrons sur cet exemple en le complétant plus tard.

Voir aussi[modifier | modifier le wikicode]

La programmation des composants[modifier | modifier le wikicode]

Une fois le programme réalisé et compilé, il est temps de l'envoyer dans l'EPROM du microcontrôleur. C'est à ce problème que nous allons nous attaquer maintenant.

Le programmateur avrdude[modifier | modifier le wikicode]

Le programmateur avrdude est un programmateur en ligne de commande assez connu dans le monde des AVR. L'IDE de l'Arduino l'utilise de manière transparente. Nous n'allons donc pas insister sur le sujet. Vous pouvez cependant lire la section sur l’utilisation de platine Arduino avec le compilateur C (sans l'IDE Arduino) pour voir un peu la ligne de commande "avrdude" !

Les bootloaders[modifier | modifier le wikicode]

La notion de bootloader a été abordée dans le chapitre précédent. Nous y revenons ici pour les replacer dans le contexte de l’Arduino.

Le comportement des bootloaders dépendent fortement du type de carte Arduino utilisé. Par exemple la carte UNO et MEGA2560 il n'y a rien de particulier à faire pour appelé le bootloader tandis qu'avec la carte LEONARDO un bouton de reset doit êtrte activé pour lancer celui-ci.

Le protocole utilisé par les bootloaders est en général le protocole STK500.

Le Timer 0[modifier | modifier le wikicode]

Nous avons déjà décrit auparavant dans ce livre le timer 0 et nous invitons le lecteur à relire le chapitre correspondant. Nous allons simplement traduire ce que nous avons déjà fait en langage Arduino. Pour ceux qui sont habitués à nos schéma nous présentons de nouveau le fonctionnement du timer 1 dans l'ATMega328 :

Documentation du Timer 0 de l'ATMega328
Panneau d’avertissement

Tout travail direct sur le timer 0 provoquera obligatoirement des dysfonctionnements des primitives Arduino :

Rappel de la documentation du shield utilisé[modifier | modifier le wikicode]

Puisque nous allons utiliser les LEDs pour afficher une valeur binaire sur 8 bits, voici la documentation correspondante :

Numéro f5 f4 f3 f2 f1 f0 p1 p0
Couleur r o v r o v v r
Arduino Pin 13 12 11 10 9 8 7 6
Port Arduino UNO PB5 PB4 PB3 PB2 PB1 PB0 PD7 PD6
Port Arduino LEONARDO PC7 PD6 PB7 PB6 PB5 PB4 PE6 PD7

Seule la ligne correspondant à l'Arduino LEONARDO nous intéresse dans la suite.

Exercice 1[modifier | modifier le wikicode]

Cet exercice est une variation d'un exercice un peu similaire dans le chapitre sur le timer 0.

Question 1[modifier | modifier le wikicode]

Le site : convert base vous propose un algorithme de division par 10 que voici :

unsigned int A;
unsigned int Q; /* the quotient */
Q = ((A >> 1) + A) >> 1; /* Q = A*0.11 */
Q = ((Q >> 4) + Q) ; /* Q = A*0.110011 */
Q = ((Q >> 8) + Q) >> 3; /* Q = A*0.00011001100110011 */
/* either Q = A/10 or Q+1 = A/10 for all A < 534,890 */

Sans chercher à comprendre l'algorithme de division, on vous demande de le transformer en une fonction de prototype :

unsigned int div10(unsigned int A);
Indication[modifier | modifier le wikicode]
unsigned int div10(unsigned int A){
  // a compléter ICI : ceci a été réalisé en TD en salle !!!
}

Ne pas chercher à réaliser un test pour le moment mais réaliser une compilation pour retirer les fautes de syntaxe.

Question 2[modifier | modifier le wikicode]

Les LEDs du shield maison sont couplées à un arduino UNO. Écrire un sous-programme capable d'afficher un nombre sur 8 bits sur les LEDs. Réaliser un programme de test à l'aide d'un compteur par exemple et/ou mieux qui utilise la division par 10 et sort sur les LEDs.

Indication[modifier | modifier le wikicode]

Comme l'indique le commentaire ci-dessous, le sous-programme que vous réaliserez ne devra en aucun cas changer d'autres bits que ceux que l’on utilise pour l’affichage des LEDs !!!

//************************************
// Ne modifie que les bits concernés
// pour les deux PORTs concernés
//************************************
void afficheLeds(unsigned char ch){
  // A compléter ici. On n'utilisera que des décalages
  //et des masques pour réaliser cette fonction
}

Question 3[modifier | modifier le wikicode]

Écrire un programme complet qui mesure le temps d'exécution du sous programme de division par 10 avec le timer 0, puis modifier le programme pour qu’il puisse comparer avec une division par 10 normale. On pourra utiliser un front montant sur le bouton A pour choisir le type de division réalisé.

Indications[modifier | modifier le wikicode]

La mise au point peut être longue pour choisir correctement le nombre de boucles de calculs nécessaires pour avoir une affichage correct sur 8 bits... ainsi que la valeur du pré-scaler.

Nous trouvons sur la page de documentation le tableau suivant :

Bouton Position Arduino Pin Port Interruption Niveau logique de l'entrée arduino si bouton appuyé
A Bas Gauche 2 PD2 int0 1
D Haut Gauche 3 PD3 int1 1
B Bas Droite A0 PC0 0
C Haut Droite A1 PC1 0

Un programme gérant la détection de front ressemblera à :

  char etatPresent=0,etatPasse=0;
  unsigned char etatSortie=0;
void setup()
{
..... // configuration des e/s
}
void loop()
{
  etatPasse=etatPresent; // mémorise l'état précédent (le présent devient le passé)
  etatPresent=digitalRead(??); // lecture de la valeur actuelle
  if ( ( etatPresent == ?? ) && ( etatPasse == ?? ) ) // si appui alors ....
  {
     .....
  }
}

Question 4[modifier | modifier le wikicode]

Modifier le programme de la question 3 pour qu'au lieu d'afficher sur des LEDs, l’affichage se fasse par la liaison série. On prendra soin d'afficher en même temps le type d'algorithme utilisé en même temps que la durée. On pourra améliorer l’affichage en affichant la durée en ms. Ceci sera fait dans un premier temps en utilisant directement le timer 0, puis dans un deuxième temps en utilisant la primitive Millis.

Indication[modifier | modifier le wikicode]

  • Vous ne pouvez pas utiliser la primitive delay pour ne pas saturer la liaison série car toute manipulation du timer 0 l'empêchera de fonctionner normalement.
  • Serial est la documentation sur la liaison série
  • Millis donne les millisecondes écoulées depuis le début du programme

La primitive Millis utilise le timer 0 de manière transparente. Mais vous ne pourrez pas l’utiliser si vous utilisez déjà le timer 0.

Question 5 : le mode de scrutation du flag[modifier | modifier le wikicode]

Nous devons savoir à ce niveau, que tout débordement du timer0 (passage de 0xFF à 0x00) entraîne le positionnement du flag TOV0, bit b0 du registre TIFR0. Vous pouvez donc utiliser ce flag pour déterminer si vous avez eu débordement du timer0, ou, en d’autres termes, si le temps programmé est écoulé. Cette méthode à l’inconvénient de vous faire perdre du temps inutilement dans une boucle d'attente.

Petit exemple :

  while ((TIFR0 & 0x01) == 0); //attente passive

Réaliser un chenillard sur les 4 LEDs en utilisant le sous programme de la question 2 et le timer 0 (à régler correctement pour un chenillard visible).

Indications[modifier | modifier le wikicode]

On donne le programme suivant concernant le timer 0 :

int main(void){
// initialisation du timer division par 8
  TCCR0 = 0x02; // prescaler 8 , entrée sur quartz
  TCNT0 = 0x00; // tmr0 : début du comptage dans 2 cycles
// bit PB0 du PORTB en sortie
  DDRB |= 0x01; //PB0 as output
  while(1) {
    TIFR0 |= 0x01; // clr TOV0 with 1 : obligatoire !!!
    while ((TIFR0 & (0x01<<TOV0)) == 0);
  // ce qui est fait ici est fait tous les 256 comptages de TCNT0
    PORTB ^= 0x01; // on bascule avec ou exclusif
  // TIFR0 &= ~(1 << TOV0); // reset the overflow flag
  }
  return 0;
}

Pouvez-vous donner la fréquence d'oscillation du bit b0 du PORTB avec quelques explications ? Modifiez-le pour le transformer avec un setup() et un loop() et une fréquence visible à l’œil si votre quartz est à 16 MHz et que votre oeil ne peut distinguer que les fréquences inférieures à 25 Hz (2 à 5 Hz serait très bien pour ce chenillard).

Exercice 2[modifier | modifier le wikicode]

Reprendre l'exercice du chapitre sur le timer 0 sur la comparaison simple en utilisant le langage Arduino.

Le Timer 1[modifier | modifier le wikicode]

Nous avons déjà décrit auparavant dans ce livre le timer 1 et nous invitons le lecteur à relire le chapitre correspondant. Nous allons simplement traduire ce que nous avons déjà fait en langage Arduino. Pour ceux qui sont habitués à nos schéma nous présentons de nouveau le fonctionnement du timer 1 dans l'ATMega328 :

Documentation du Timer 1 de l'ATMega328

Le timer 1 en interruption[modifier | modifier le wikicode]

Nous allons chercher à déclencher une interruption par le timer 1. Cette interruption va faire basculer la broche 11 de la maquette Arduino qui est relée par l'intermédiaire d'un shield à une LED.

#define ledPin 11

void setup(){
  pinMode(ledPin, OUTPUT);
// initialize Timer1
  noInterrupts(); // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 34286; // preload timer 65536-16MHz/256/2Hz
  TCCR1B |= (1 << CS12); // 256 prescaler
  TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt
  interrupts(); // enable all interrupts
}
	 
ISR(TIMER1_OVF_vect) {// interrupt service routine that wraps a user defined function supplied by attachInterrupt
  TCNT1 = 34286; // preload timer
  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
}
	 
void loop(){
	// your program here...
}

Même si ce programme fonctionne très bien, permettez-nous de faire les remarques suivantes.

Ces remarques ne remettent pas en cause l’utilisation des fonctions Arduino mais il faut garder en tête que celles-ci sont utiles pour débuter. Une fois formé débarrassez-vous en pour programmer en C le plus vite possible.

Utilisation du timer 1 en comparaison[modifier | modifier le wikicode]

La comparaison avec le timer 1

Le matériel s'occupe de tout[modifier | modifier le wikicode]

Nous donnons la version qui fonctionne sur la platine MEGA2560. Pour une autre platine il faut probablement changer le numéro de broche.

//*********** Pour platine MEGA2560 mais fonctionne probablement avec platine UN0
// probablement à changer pour une autre platine
#define ledPin 11

void setup(){ 
// initialisation pour comparaison
// DDRB |= (1<<DDB5); // 1 = sortie
  pinMode(11,OUTPUT);
  noInterrupts(); // disable all interrupts
  TCCR1B = 0x00;
  TCCR1A = 0x00;
  TCNT1 = 0x0000;
  digitalWrite(11,1);// OCR pin
  //TIFR0 |= 0x04; // clr TOV1 with 1 : inutile
  // Prescaler 1024 (Clock/1024) 
  TCCR1B |= (1<<CS12) | (1<<CS10);
  TCCR1B |= (1<<WGM12); //RAZ timer quand comparaison
  TCCR1A |= (1<<COM1A0); //bascule sortie a chaque comparaison
// TCCR1A &= ~(1<<COM1A1); //inutile : tout est déjà à 0
  OCR1A = 7812;//16 000 000 /(1024*2)
}

void loop() {
  // Your program here...  
}

Vous pouvez constater qu'aucune interruption n'est lancée. C'est le matériel qui s'occupe d'absolument tout, tout seul comme un grand.

Comparaison et interruption[modifier | modifier le wikicode]

Pour nous la patte 9 est reliée à ne LED. On peut donc visualiser son clignotement.

#define ledPin 9 
void setup(){
	pinMode(ledPin, OUTPUT);	 
	// initialize Timer1
	noInterrupts(); // disable all interrupts
	TCCR1A = 0;
	TCCR1B = 0;
	TCNT1 = 0;
	 
	OCR1A = 31250; // compare match register 16MHz/256/2Hz
	TCCR1B |= (1 << WGM12); // CTC mode
	TCCR1B |= (1 << CS12); // 256 prescaler
	TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
	interrupts(); // enable all interrupts
}
 
ISR(TIMER1_COMPA_vect) // timer compare interrupt service routine
{
	digitalWrite(ledPin, digitalRead(ledPin) ^ 1); // toggle LED pin
}
	 
void loop(){
	// your program here...
}

Le Timer 2[modifier | modifier le wikicode]

Mesure de temps d'exécution[modifier | modifier le wikicode]

Nous allons profiter du timer 2 pour mesurer le temps d'exécution de certaines fonctions Arduino, et se rendre compte de l'une de leur principale limite.

Le temps écoulé sera mesuré à l'aide du Timer 2.

Le principe est toujours le même :

  • Initialiser le timer
  • Répéter plusieurs fois l'instruction souhaitée
  • Observer la valeur du timer
  • Afficher cette valeur

Le timer 2 et ses registres (sans interruption)[modifier | modifier le wikicode]

Documentation simple du Timer 2 (8 bits)

Voici ci-contre, avec les conventions schématiques habituelles, le schéma de fonctionnement du timer 2.

On distingue ainsi le bit b0 du registre TIFR2 appelé TOV2 qui est mis à un automatiquement par le matériel. Ce ne sera pas la matériel qui le mettra à 0 s'il n'y a pas d'interruptions. C'est à vous programmeur de le faire ! Mais pour le faire il vous faut écrire un 1 dans ce bit b0 !

Les habituels bits de configuration de la division se trouvent dans le registre TCCR2B et fonctionnent exactement comme pour timer 0.

Le registre ASSR sert à choisir la source de l'horloge du timer 2. Pour nous, sauf mention contraire, ce sera toujours le quartz. Ce registre doit être configuré dans ce mode de fonctionnement par défaut.

Configuration des entrées/sorties[modifier | modifier le wikicode]

Nous souhaitons ici évaluer le temps mis par la fonction pinMode().

L'affichage du résultat sera tout simplement transmis sur la liaison série en utilisant les fonctions suivantes :

  • Serial.begin(debit)
  • Serial.print("valeur = ")
  • Serial.println(valeur,DEC)

Question : Compléter le programme suivant en choisissant les bonnes valeurs pour évaluer le temps d'exécution de pinMode()

void setup() {
// Variables éventuelles

// Initialisation du port série

  Serial.begin(9600);

// Configuration du timer 2 : Attention, chaque bit doit être configuré à '0' ou '1'
  TCCR2A ??? (1<<WGM20); // mettre à 0
  TCCR2A ??? (1<<WGM21); // mettre à 0
  TCCR2B ??? (1<<WGM22); // mettre à 0
// choix du pré-diviseur : 
  TCCR2B ??? (1<<CS22);
  TCCR2B ??? (1<<CS21);
  TCCR2B ??? (1<<CS20);
// Initialisation du Timer : acquittement et valeur de départ
  TIFR2|=1<<TOV2;
  TCNT2=0;
  for (i=0;i<1;i++) // Le nombre d'itérations peut/doit être adapté !
  {
// Fonction à évaluer : il est intéressant de répéter la fonction plusieurs fois
    pinMode(6,OUTPUT);
  }
// Récupérer la valeur du timer et l'afficher seulement si le timer n'a pas débordé !!!
}

void loop() {

}

Attention ! Ne prenez pas ce que vous donne le timer pour argent comptant. Cette valeur doit avoir certaines propriétés :

  • ne pas être accompagnée d'un débordement de TCNT2. Vous seriez donc très inspiré d'écrire la valeur du flag TOV2 avec la liaison série en plus de la valeur de temps
  • augmenter lorsqu'on augmente le nombre de boucles exécutées sur l'instruction testée (autre manière de dire les choses, on double le temps si on double le nombre de boucles).

Ce travail nécessite donc un peu de soin et beaucoup d'essais pour trouver la bonne valeur de la division dans le pré-diviseur à choisir. Il peut être utile de mettre les tests dans loop() pour prendre son temps pour ouvrir la liaison série.

Question : Comparer en déclarant une sortie directement en configurant le registre DDRx

Interruptions[modifier | modifier le wikicode]

L'attente passive peut être évitée à l'aide des interruptions. On imagine un programme qui fait des tas de choses mais qui sera interrompu régulièrement par un timer.

La comparaison avec le Timer 2 (8 bits)

Question : En utilisant la documentation ci-dessus, on vous demande de générer un signal d'un kilo Hertz à l'aide d'une interruption.

Indication[modifier | modifier le wikicode]

Voici un premier programme à compléter :

void setup()
{
  DDRB =      ; // Pin 6 as output
// Using timer 2
// Set to Normal mode, Pin OC0A disconnected
  TCCR2A =        ;
// Prescale clock by 1024
// Interrupt every 256K/16M sec = 1/64 sec
  TCCR2B =        ;
// Turn on timer overflow interrupt flag
  TIMSK2 =        ;
// Turn on global interrupts
  sei();
}
volatile char timer = 0;

ISR( _vect) {
  timer++;
  PORTB =        ;
}

void loop()
{
// Nothing to do
}

La comparaison[modifier | modifier le wikicode]

Documentation[modifier | modifier le wikicode]

La comparaison avec le Timer 2 (8 bits)

Voici la documentation correspondante sous forme de schéma (ci-contre).

Ce mode de fonctionnement s’appelle la comparaison. L’idée générale est que lorsque le timer 2 (TCNT2) arrive à la même valeur que celle qui est contenue dans un registre (OCR2A ou OCR2B) une logique interne est capable de changer (ou pas) une sortie qui s’appelle OC2A ou OC2B.

Ce mode est essentiellement géré par les deux bits COM2A1 et COM2A0 comme indiqué dans le tableau ci-dessous :

Mode non PWM pour la comparaison
COM2A1 COM2A0 Description
0 0 Opération Normale PORT, OC2A déconnecté
0 1 Bascule OC2A sur la comparaison
1 0 Mise à 0 de OC2A sur la comparaison
1 1 Mise à 1 de OC2A sur la comparaison

Nous avons ajouté les informations correspondant à la carte UNO entre parenthèse. Par exemple la sortie correspondante au bit b3 du PORTD est OC2A et correspond au numéro 11 de la carte UNO. L'autre broche gérée par le comparateur B est la broche 3 et se trouve entre parenthèse avec le registre de comparaison.

Utilisation du timer 2 en mode CTC[modifier | modifier le wikicode]

Le mode CTC du timer est un mode qui veut dire : Clear Timer on Compare match.

Question : Faire un programme qui génère un signal de fréquence 1kHz sur la patte PB3, en utilisant le timer 2 et la comparaison.

Indication : l’idée est de placer le timer en mode CTC (Clear Timer on Compare match) et la génération de signaux en mode basculement de OC2A.

Description des bits pour le mode de fonctionnement du timer 2 CTC
Mode WGM22 WGM21 WGM20 Mode de fonctionnement Bas si Mise à jour de OCRAx si Drapeau TOV2 positionné si
2 0 1 0 CTC OCR2A immédiatement MAX

Utilisation du timer 2 pour générer deux signaux[modifier | modifier le wikicode]

Il est possible de générer deux signaux d'un coup avec le timer2 cat il y a un seul timer mais deux registres de comparaison.

Question : Ajouter au programme précédent une génération de signal de fréquence 2kHz sur la patte PD3, en utilisant le timer 2 et la comparaison. À ce stade on aura deux fréquences générées avec aucune occupation du processeur (loop complètement vide).

Interruption sur comparaison[modifier | modifier le wikicode]

Il est possible aussi de déclencher une interruption sur une comparaison. Voici un squelette de programme à compléter pour cela :

void setup()
{
  DDRB =          ; // Pin 13 OUTPUT
// Using timer 2
// Set to CTC mode, Pin OC0A disconnected
  TCCR2A =         ;
// Prescale clock by 1 (no prescale)
  TCCR2B =         ;
// Set compare register
  OCR2A =          ;
// Turn on timer compare A interrupt flag
  TIMSK2 =         ;
// Turn on global interrupts
  sei();
}
char timer = 0;
ISR( _vect) {
  timer++;
  PORTB =       ;
}
void loop(){
// Nothing to do
}

La suite et parfois des compléments peuvent être trouvés dans le WIKI de l'IUT de Troyes.

Clavier USB avec la platine Leonardo[modifier | modifier le wikicode]

La platine Leonardo est architecturée autour d'un processeur ATMega32U4, le U signifiant USB. Cette version de processeur permet de gérer deux protocoles USB clients en même temps, nous voulons dire par la même prise USB. Nous verrons dans le projet de cette section (et dans l'exemple keyboardSerial de la rubrique USB) qu’il est possible d’utiliser ensemble la liaison série et un clavier USB par le même fil USB.

En fonctionnement normal de la platine Leonardo, la liaison série est utilisée pour la programmation à l'aide d'un bootloader à travers l'USB. Les connaisseurs d'autres platines Arduino (comme la UNO et bien d'autres) diront qu’à première vue, il n'y a rien de nouveau ! Sauf qu'ici le processeur se débrouille seul alors que dans les premières versions de la platine UNO il y avait un FTDI2232 qui transformait l'USB en RS232. Les nouvelles versions de cette platine ont abandonné le FTDI au profit d'un processeur ATMega8u2 ou ATMega16u2... et tout cela en plus du processeur ATMega328 que l’on cherche à programmer. La Leonardo ne comporte que le processeur à programmer et rien d'autre, ni FTDI ni autre processeur.

Nous allons examiner les performances de la Leonardo par rapport à cette liaison USB. Pour ce faire, nous allons simuler une clé USB destinée à entrer un identifiant et un mot de passe automatiquement. C'est un projet assez courant sur Internet mais nous n'avons trouvé que peu de code source (Celui-ci fait exception mais est très loin de réaliser notre cahier des charges).

Cahier des charges[modifier | modifier le wikicode]

Ce que nous cherchons à faire peut être appelé "USB keylogger" en anglais (ou à peu près car le mot keylogger est associé parfois à l'enregistrement pirate de frappes au clavier). Aussi le mot anglais USB EasyLogger semble plus approprié. Nous le traduisons par clé de connexion USB. Nous allons donc développer la partie logicielle sur une platine Leonardo. La partie matérielle finale devra être compatible Leonardo. Elle est décrite plus loin. Nous voulons être capable de :

  • stoker plusieurs identifiants et mots de passes en EEPROM par la liaison série/USB
  • effacer certains couples identifiants/mots de passes plus utilisés
  • choisir lequel est actif par défaut
  • choisir la séquence par défaut

Le dernier point mérite quelques explications. Les comptes éloignés nécessitent en général un identifiant et un mot de passe. Les deux sont séparés par une tabulation et après le mot de passe on valide par un ENTER. Mais sur votre ordinateur local, le bon utilisateur est parfois choisi par défaut et donc seul le mot de passe est demandé. C'est cela que nous appelons séquence et qui pourra être choisi.

Notre description de cette clé de connexion USB nécessitera une utilisation fréquente des expressions :

  • Mot de Passe que nous résumerons le plus possible par MdP
  • Utilisateur (ou mieux identifiant) que nous appellerons aussi par l'anglicisme login
  • couple "Identifiant/Mot de passe" sera souvent appelé login/MdP

Pour respecter ce cahier des charges, nous envisageons d’utiliser quatre boutons :

  • un bouton appelé ENTER pour envoyer le couple login/MdP,
  • un bouton appelé MODIF qui devra permettre d'entrer dans un mode modification à l'aide d'un hyperterminal,
  • un bouton appelé CHOIX qui devra aussi permettre des modifications en utilisant uniquement les trois LEDs présentes.
  • un reset comme il y a sur le Leonardo

Dans l'état actuel seuls les boutons ENTER et MODIF sont utilisés.

Pourquoi prendre une Leonardo comme point de départ ?[modifier | modifier le wikicode]

Nous sommes parti d'une platine Leonardo, car elle fait partie des platines qui gèrent le périphérique clavier en natif. Cela est dû essentiellement au processeur ATMega32u4 utilisé. En fait toute platine compatible Arduino avec ce processeur peut être utilisée. Vous avez par exemple :

  • Platine "Pro Micro - 5V/16MHz"
  • Platine "Pro Micro - 3,3 V/8MHz"

et d'autres encore du fabricant Sparkfun. Celles-ci sont de très petites tailles (3,4 cm x 1,7 cm environ) mais possèdent un connecteur micro USB.

La platine Leonardo munie d'un shield avec trois boutons peut être utilisée pour le développement logiciel. Le développement de cette partie logicielle est assez longue. Ce ne serait pas le cas si vous n'aviez comme objectif d’utiliser un login et mot de passe seulement. Mais si vous voulez pouvoir les mettre à jour avec un micro-ordinateur Windows, Linux ou MAC c’est une autre histoire.

Le développement logiciel avec l'environnement Arduino est particulièrement simple mais présente un inconvénient pour la réalisation finale : le bootloader. En effet celui-ci attend 8s avant de lancer le programme. Cela veut dire que chaque fois que vous allez brancher votre clé de connexion USB, il vous faudra attendre 8s avant de pouvoir envoyer votre login et mot de passe. Cela peut être considéré comme un inconvénient grave. Si c’est le cas il faut pouvoir modifier le fusible qui gère le RESET (sur bootloader ou pas) et ceci nécessite une programmation ICSP en dehors de l'environnement Arduino !


Il est grand temps d’aborder les problèmes spécifiques des claviers USB.

Clavier QWERTY[modifier | modifier le wikicode]

Nous avions cru lire, il y a quelques temps sur Internet, que les claviers USB ne retournent plus les scancodes comme leurs cousins PS/2 mais surtout que le code retourné était absolu : 'a' codé 0x04, 'b' codé 0x05. Mais ceci est malheureusement faux. Cela prouve que notre anglais n’est pas encore assez pointu pour distinguer ce genre de détail. Il faut dire qu’il est très difficile de trouver de la documentation technique sur le sujet.

Nous avons lu aussi qu'avec Leonardo on était condamné au clavier QWERTY contrairement à son concurrent Olimexino. Et bien comment dire, c’est aussi faux !

Un théorème dit qu' en programmation tout est possible et son corollaire dit tout dépend du temps que l’on désire y passer. Il n'y a pas de mathématiques là-dedans mais nous avons toujours adoré utiliser de la terminologie mathématique là où il n'y en avait pas besoin !

L'instruction qui permet d'écrire comme un clavier sera détaillée plus tard, mais elle s’appelle Keyboard.println(). Et bien figurez-vous que si vous utilisez Keyboard.println("mouton") sur appui d'un bouton et que vous ouvrez un éditeur de texte et appuyez sur ledit bouton vous obtiendrez ",outon" ! Tout simplement parce que le m (AZERTY) est remplacé par une virgule sur clavier QWERTY ! C'est une mauvaise nouvelle car pour corriger, ce n’est pas difficile mais long. La bonne nouvelle c’est que nous allons essayer d'expliquer comment faire, le faire, et finalement le publier ici.


Tableau de transcodage QWERTY[modifier | modifier le wikicode]

Chercher le fichier HID.cpp de votre distribution Arduino. Chez nous, sous Linux, il est dans "/usr/share/arduino/hardware/arduino/cores/arduino". Chez vous, sous Windows, il est probablement dans quelque chose comme "Program File\arduino\hardware\arduino\cores\arduino".

Dans le fichier HID.cpp vous trouverez un tableau de transcodage :

Nous le donnons ici car c’est la version originale du fichier HID.cpp et il vous faut le mettre de côté.

Changer QWERTY en AZERTY[modifier | modifier le wikicode]

C'est le tableau de transcodage de la section précédente qu’il faut changer. Voici comment procéder techniquement pour le changer.

Essayez de vous procurer une photo d'un clavier QWERTY. Nous en dessinons un (très simplifié) ci-dessous.

Claviers QWERTY et AZERTY simplifiés et leurs scancodes (en rouge)

Le point essentiel est de comprendre que votre tableau de transcodage doit correspondre au clavier du bas alors que celui que vous avez au départ correspond à celui du haut ! Les échanges de codes sont faciles lorsque vous savez à quelle touche QWERTY correspond telle lettre. Vous la cherchez sur le clavier AZERTY et notez le numéro rouge s'il est différent et le placez à l'endroit du commentaire correspondant.

Les choses sont plus délicates lorsque vous n'avez pas la lettre correspondante sur le clavier QWERTY. Prenez le é par exemple, quel est son code ? Ce n’est pas 0X1F comme le laisserait penser le dessin du bas ! ... et de toute façon il n'est même pas en commentaire dans le fichier HID.cpp !!!

Ce tableau de transcodage permet d'exécuter un programme du genre :

void setup() {
  pinMode(2,INPUT);
}

void loop() {
  static char new_button,old_button;
  new_button=digitalRead(2);
  if ((old_button==0)&&(new_button==1)) { //detection front montant
   Keyboard.begin();
   Keyboard.println("DEBUT AUTORISE");
   Keyboard.println("abcdefghijklmnopqrstuvwxyz");
   Keyboard.println("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
   Keyboard.println("0123456789");
   Keyboard.println(" !\"$%&'()");
   Keyboard.println("%,;:!?./+=");
   Keyboard.println("FIN AUTORISE");
   Keyboard.println("DEBUT NON AUTORISE");
   Keyboard.println("<>*#{[|`\^@]}");
   Keyboard.println("FIN NON AUTORISE");
   Keyboard.end();
  } 
  old_button = new_button;
  delay(100); 
}

avec comme résultat :

DEBUT AUTORISE
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
0123456789
 !"$%&'()
%,;:!?./+=
FIN AUTORISE
DEBUT NON AUTORISE
−−	"ïµ²ếj£
FIN NON AUTORISE

où l’on voit que les caractères non autorisés ne fonctionnent pas correctement. Pour le constater, comparer ce qui est affiché avec ce qui est mis dans les println du programme correspondant (au-dessus) ! En clair, ce que nous avons noté NON AUTORISE correspond aux caractères que vous ne pouvez pas utiliser dans vos mots de passe.

Passer ces caractères non autorisés comme autorisés demande un travail supplémentaire prévu dans le futur mais pas réalisé pour le moment. Il faut pour cela changer le code source de HID.cpp, temporairement au moins pour examiner les scancodes.

... et qui est suivie dans la section suivante.

Plus loin pour changer QWERTY en AZERTY[modifier | modifier le wikicode]

Le code ci-dessous force systématiquement l’utilisation de l'envoi d'un scancode au lieu d'un code ASCII à cause du #define ASCII_EN en commentaire au début. Il force aussi systématiquement le modificateur Alt droit qui est pour nous français un Alt Gr. Le résultat est enfin les caractères ~#{[|`\^@]}. Mais il n'est bati que pour du test. À modifier pour poursuivre.

Autre piste en lisant attentivement le code ci-dessus pour le press et release : on peut atteindre directement le modificateur AltGr avec le code 134 comme ci-dessous :

Keyboard.press(134);
Keyboard.write('"');
Keyboard.release(134);
Keyboard.write(',');

Ceci n'écrira pas ", mais #, et c’est bien ce que l’on cherche à faire. Cette technique nous permet de récupérer tous les caractères AltGr soit ~#{[|`\^@]} Pas mal non ? Mais il faut écrire un programme à nous qui fasse les bons appels en fonction des caractères. Et ce travail n'a pas encore été fait.

Panneau d’avertissement Nous venons de découvrir que les modifications que nous proposons ci-dessus ne fonctionnent pas avec Windows 8 ! Le clavier reste désespérément en QWERTY.

Reste aussi le cas des caractères accentués : là, nous ne voyons aucune piste simple pour le moment !

Nous sommes absolument stupéfait par la difficulté de répondre à cette simple question : que faut-il envoyer au PC pour qu’il interprête cela comme un é ? Après des heures et des heures de recherche sur Internet, nous sommes incapable de répondre à la question !!!

Ceci est un appel à l'aide !!!

HELP HELP HELP !!!

HILFE HILFE HILFE !!!

Nous ne connaissons pas d’autre langues !

Travail Logiciel[modifier | modifier le wikicode]

Nous allons donner sous forme d'exercices l’ensemble du travail logiciel à réaliser.

Stocker les différents utilisateurs/ mots de passe dans l'EEPROM[modifier | modifier le wikicode]

Nous allons utiliser l'abréviation MdP pour désigner les mots de passe dans la suite. Ainsi le couple "identifiants/mots de passe" pourra s'écrire de manière simplifiée : identifiants/MdP

On vous demande de réaliser un sous-programme capable de mettre à jour l'EEPROM avec des couples identifiants/MdP. La convention est la suivante :

  • les mots de passes comme les identifiants seront des mots de 15 caractères maximum terminés systématiquement par un zéro (de fin de chaîne).
  • Le premier octet de l'EEPROM désigne le mot de passe par défaut : 0 est le premier mot de passe, 1 le deuxième etc ...
  • Le deuxième octet de l'EEPROM désigne le nombre maximum de mots de passe enregistrés dans l'EEPROM (ce nombre ne devra pas dépasser 7)
  • Le troisième octet désigne le type de séquence utilisée : soit "Login TAB MdP ENTER", soit "MdP ENTER" pour le moment
  • Le premier identifiant se trouve en adresse 0x10, le deuxième en 0x30 ....
  • Le premier mot de passe se trouve en adresse 0x20, le deuxième en 0x40 ....

Voici un dessin représentant le contenu de l'EEPROM pour un stockage de deux identifiants/mots de passe :

Comment les mots de passe et identifiants sont stockés en EEPROM

Donnons une petite mise en garde sur l’utilisation de l'EEPROM avant de continuer.

Panneau d’avertissement

N'écrivez pas pour un oui et pour un non dans l'EEPROM. C'est une ressource qui a un nombre d'écritures limité. Cela ira très bien pour une clé de connexion USB si vous n'en abusez pas. Dès que vous avez lancé ce programme vous devriez mettre init_eeprom() du setup en commentaire ! N'oubliez pas que le setup() est réalisé après chaque programmation !

Utiliser le login/MdP de l'EEPROM pour un essai[modifier | modifier le wikicode]

On vous demande maintenant d’utiliser le contenu de l'EEPROM pour tenter de vous connecter à un de vos compte. Le sous programme correspondant aura comme prototype : void fakeKeyboard(). Le mot anglais "fake" se traduit par "faire comme si".

Ouvrez un éditeur de texte pour confirmer ou infirmer son fonctionnement correct. Si vous voulez l’utiliser pour un essai grandeur nature sur un de vos comptes, il vous faut conditionner l'appel de ce sous-programme à un front montant sur une entrée quelconque.

Un sous-programme pour afficher complètement le contenu de l'EEPROM par la liaison série[modifier | modifier le wikicode]

Écrire un sous-programme capable d'afficher le contenu de l'EEPROM par la liaison série. Un paramètre option lui sera passé. Si un 1 est présent sur le bit b7 de option alors les mots de passe seront masqués par des '*'. L'identifiant/MdP par défaut devra être précédé de ">>" et la séquence sera affichée en clair. Il n'y a pas beaucoup d’intérêt à afficher l'octet contenant le nombre de couples login/MdP.

Liaison série pour faire un choix[modifier | modifier le wikicode]

La mise à jour des logins/MdP doit pouvoir se réaliser à l'aide d'un simple hyperterminal et non pas à l'aide d'un environnement Arduino.

Au fur et à mesure des difficultés rencontrées à mettre au point le programme complet, nous avons abandonné petit à petit une gestion trop complexe de menus et sous-menus. Ainsi, le nombre de choix s'est limité au fur et à mesure. Nous avons cependant mis au point un sous-programme de choix série qui peut être utile à d'autres projets.

Partant du principe qu'un utilisateur auquel on demande de choisir entre deux valeurs max et min essaiera toujours un nombre en dehors de cet intervalle, pouvez-vous mettre au point un sous-programme de prototype "char readChoixSerie(char maxmin)" qui retournera le choix de l’utilisateur que s'il est entre min et max ? Si le choix est en dehors, il redemandera à l'utilisateur de choisir et ce dernier finira bien par abandonner. Les deux nombres max et min sont codés sur 4 bits et dans un même octet (max dans les 4 bits de poids fort et min dans les 4 bits de poids faible).

Liaison série pour mettre à jour[modifier | modifier le wikicode]

Le sous-programme correspondant n’est pas encore complètement au point.

Réalisation matérielle[modifier | modifier le wikicode]

Jean-Michel Gérard, ingénieur d'étude à l'IUT de Troyes jusqu'en aout 2014, nous a réalisé une partie matérielle que nous allons présenter maintenant.

Le travail a consisté à prendre le schéma électronique libre de la platine Leonardo, à retirer tout ce qui est inutile et à ajouter trois boutons (en plus du reset). L'alimentation du Leonardo a été simplifiée. En effet, l’utilisation comme une clé nous impose systématiquement une alimentation par l'USB hôte. Pas besoin de rendre cette carte autonome avec une alimentation autre que l'USB.

Schéma de principe[modifier | modifier le wikicode]

Voici le schéma de principe retenu :

Clé de connexion USB (Jean-Michel Gérard)

Ce schéma réalisé avec Eagle est de qualité suffisante pour pouvoir être téléchargé et consulté à l'aide de zooms importants.

Les trois boutons (nommés plus loin) sont clairement visibles en haut du schéma où l’on découvre clairement qu’ils sont montés à l'aide d'une pull-up externe (pas forcément utile avec la famille AVR).

Trois LEDs sont aussi visibles en bas à droite du schéma.

Circuit imprimé[modifier | modifier le wikicode]

Le circuit a été réalisé en deux couches. Nous en présentons une image même si celle-ci ne peut pas être utilisée telle quelle.

Recherche des noms Arduino des boutons et des LEDs[modifier | modifier le wikicode]

Le standard Arduino pour les shields a été abandonné puisque l’on veut un circuit de la taille d'une clé USB (enfin un peu plus grand quand même). Ainsi les boutons ont été réalisés avec des pull-up externes mais reliés aux PORTs qui simplifiaient au mieux le circuit imprimé. Il s'agit de PB4, PB5 et PB6. Comme le nom de ces bits n’est pas utilisé en programmation Arduino, il nous faut trouver les correspondants symboliques (du langage Arduino). La technique pour cela est assez simple : il suffit de lire le schéma libre de la carte Léonardo dans cette page. C'est là qu'on voit l’intérêt d'un hardware libre.

nom des boutons CR = ENTER MODIF CHOIX
PORT PB4 PB5 PB6
Nom Arduino 8 9 10

Trois LEDs sont présentes et doivent, elles-aussi, être associées à un nom Arduino.

PORT PF5 PF6 PF7
Nom Arduino A2 A1 A0
  • Le bouton ENTER sert à envoyer le login/MdP comme on le ferait avec un clavier
  • le bouton MODIF sert à ajouter un login/MdP et à modifier le login/MdP par défaut et la séquence à l'aide d'un hyperterminal
  • le bouton CHOIX fait la même chose que MODIF mais sans hyperterminal mais avec les trois LEDs (non encore réalisé en logiciel à ce jour)

Mise en place du bootloader Leonardo[modifier | modifier le wikicode]

Puisque nous désirons programmer à partir de l'environnement Arduino il nous faut mettre le bootloader Arduino dans l'ATMega32U4. Cette opération est déjà réalisée lorsqu'on achète une platine Arduino mais pas lorsqu'on achète un 32U4 et qu'on le met sur un circuit que l’on a conçu. C'est à cause de cette opération qu'a été ajouté un programmateur ICSP 10 points. Une fois cette opération réalisée, le programmateur pourra être retiré si l’on veut un peu moins de volume à notre clé.

Mettre un bootloader dans un processeur n’est pas une opération difficile pour qui a déjà programmé un AVR en dehors de l'environnement Arduino. Par contre configurer correctement les fusibles est une autre histoire. Pour le moment nous allons nous contenter d'une copie d'écran concernant l'ATMega32U4 avec un environnement assez lisible :

Les fusibles du 32U4 pour fonctionnement compatible Arduino

Cette image devra être discutée pour une bonne compréhension. Mais ce sera fait plus tard.

Voir aussi[modifier | modifier le wikicode]

Nous avons rédigé un énoncé de projet sur ce thème dans le WIKI de l'IUT de Troyes :

  • Clef USB reprend la problématique ci-dessus en commençant par travailler en RAM plutôt qu'en EEPROM. C'est quand les programmes seront au point que l’on passe à l’utilisation de l'EEPROM. Le but est d'économiser un peu l'EEPROM.

Et maintenant un site qui nous doit beaucoup :


Nous allons maintenant passer à un tout autre domaine : la réalisation d'un algorithme CORDIC dans un Arduino.

CORDIC sur Arduino[modifier | modifier le wikicode]

Nous allons évoquer le calcul de cosinus et sinus en format virgule fixe et ses performances.

Les besoins de tels calculs se retrouvent dans la régulation d'un moteur synchrone comme dans la robotique mobile. Mais l’utilisation de flottants avec un Arduino est très coûteux. Il faut donc l'éviter à tout prix. Pour cela on dispose du format virgule fixe que nous allons expliciter maintenant.

La représentation virgule fixe[modifier | modifier le wikicode]

Le format Virgule fixe n’est pas normalisé contrairement au format virgule flottante. Le seul point commun que l’on retrouve dans tous les formats virgule fixe est qu’il est signé. Il peut donc représenter des nombres positifs ou négatifs en complément à 2. La virgule étant placée par défaut à un endroit fixe on utilise en général une notation du genre Qm.n pour le désigner où m représente le nombre de bits avant la virgule et n le nombre de bit après la virgule et donc m+n est la taille de la représentation.

Par exemple nous allons utiliser un format Q3.13 dans la suite de ce chapitre.

S

Il s'agit d'un format 16 bits (3+13) et adapté à la trigonométrie en radian.

Puisque nous sommes intéressé par la conversion de ce format en flottant, nous allons présenter maintenant ce format qui est normalisé.

La représentation virgule flottante[modifier | modifier le wikicode]

L'article Virgule_flottante de wikipédia est suffisamment complet pour ne pas être repris ici. Il peut d'ailleurs être complété par la lecture de l’article détaillé sur la norme IEEE 754 correspondante.

Voici en figure récapitulative le codage des nombres flottants.

Format général de représentation des flottants
Format général de représentation des flottants

Il existe essentiellement deux formats :

Encodage Signe Exposant Mantisse Valeur d'un nombre Précision Chiffres significatifs
Simple précision 32 bits 1 bit 8 bits 23 bits 24 bits environ 7
Double précision 64 bits 1 bit 11 bits 52 bits 53 bits environ 16

Sur ces deux formats, seul le format simple précision sur 32 bits nous intéresse. Il peut se résumer avec la figure :

Format de représentation des flottants en simple précision
Format de représentation des flottants en simple précision

Transformation virgule fixe vers nombre réel et vice versa[modifier | modifier le wikicode]

Si le cœur vous en dit, il vous est possible d'expérimenter l'écriture directe de nombre flottant en hexadécimal avec ensuite un affichage. Cela permet de comprendre un peu la mécanique des flottants. Voici par exemple un programme complet :

typedef union {
     float f_temp;       //taille : 4 octets
     long int li_temp;  //taille : 4 octets
  } t_u;

void setup(){
  Serial.begin(9600);
}

void loop(){
  t_u u;
  u.li_temp = 0X3F400000;
  Serial.println(u.f_temp,4);
  delay(5000);
}

Remarquez que l’on utilise une "union" pour définir en même temps 32 bits manipulables directement et le flottant associé. Cette façon de faire sera utilisée lors des conversions. Ce code affiche sans arrêt 0.7500 : auriez-vous été capable de prévoir cela ?

Le code présenté maintenant est lié au travail réalisé pour un autre livre sur VHDL (chapitre CORDIC). Nous présentons donc un code compact adapté à l'embarqué.

Le calcul CORDIC et son test associé[modifier | modifier le wikicode]

L'article CORDIC de wikipédia donne un morceau de code en C. Ce code de principe utilise le type float ainsi que des opérations sur ce flottants. Nous avons déjà eu l’occasion d’en présenter une transformation dans le chapitre sur la base robotique mobile ASURO. En voici maintenant une version complètement Arduino qui utilise la liaison série pour demander un angle et utilise cette même liaison pour afficher le résultat du calcul.

Des comparaisons avec une calculette montrent trois chiffres significatifs corrects ce qui est un bon résultat. Pour information, la taille du binaire correspondant à ce code est de 5784 octets contre plus de 8000 pour le code initial. Retirer "int float2HexQ3_13(float val)" qui n’est pas toujours utile permet de descendre à 3900 octets... et ajouter un "Serial.println(cos(angle));" nous amène tout de suite à 7148 octets ! Ces chiffres ne sont pas importants en soi mais nous montrent que tout programmeur doit avoir un peu de réticence à utiliser des types flottants.

Voir aussi sur CORDIC[modifier | modifier le wikicode]

SPI maître/esclave avec Arduino[modifier | modifier le wikicode]

Nous allons laisser ici un exemple de code pour connecter deux Arduinos :

  • le premier est un Arduino nano et sera considéré comme le maître
  • le deuxième est un Arduino UNO et sera considéré comme esclave

Le type de carte Arduino utilisé n'a en fait que peu de répercussion sur le code présenté plus bas. Seuls les branchements sont dépendants du type de carte Arduino utilisée.

Broches SPI MOSI MISO SCK SS
Arduino UNO Pin (esclave) 11 12 13 10
Arduino Nano Pin (Maître) P11 P12 P13 P10 ou autre

Notre objectif est de réaliser une configuration telle que bien évidemment c'est le maître qui décide la demande à l'esclave de données. L'esclave est donc supposé faire des calculs sans arrêt et livrer ses données seulement quand le maître le demande.

Réalisation du maître[modifier | modifier le wikicode]

La réalisation du maître ne pose aucune difficulté car la librairie Arduino standard peut être utilisée pour cela. Voici donc le code.

#include <SPI.h>

const int slaveSelectPin = 10; //n'importe quel bit de port peut être utilise ici

void setup() {
  // set the slaveSelectPin as an output:
  Serial.begin(9600);
  pinMode(slaveSelectPin, OUTPUT);
  // initialize SPI:
  SPI.begin();
}

void loop() {
  uint8_t dataSlave;
  digitalWrite(slaveSelectPin, LOW);
  dataSlave=SPI.transfer(0);
  digitalWrite(slaveSelectPin, HIGH);
  Serial.println(dataSlave);
  delay(1000);
}

Réalisation de l'esclave[modifier | modifier le wikicode]

La réalisation de l'esclave demande un peu plus d'attention. Nous avons en effet décidé d'utiliser la fonction standard d'initialisation de la librairie SPI (à savoir SPI.begin()) qui ne sait qu'initialiser en SPI Maître. Il ne faut donc pas oublier de passer MISO en sortie (sur l'esclave) et MOSI en entrée.

Il faut aussi naturellement passer le bit MSTR du registre SPCR à 0 pour passer en mode esclave.

L'esclave fonctionnera en interrruption et sera occupé à faire des calculs le reste du temps.

#include <SPI.h>
const int slaveSelectPin = 10; // absolument obligatoire pour un esclave !!!!

volatile boolean process;
uint8_t cmpt=0;
volatile uint8_t c=0;

void setup() {
  // set the slaveSelectPin as an output:
  pinMode(slaveSelectPin, INPUT);
  // initialize SPI:
  SPI.begin();
  // slave select
   //SPCR |= (1<<SPE)|(1<<SPIE);
   SPCR &= ~(1<<MSTR); // turn on SPI in slave mode
   SPI.attachInterrupt(); // turn on interrupt
   pinMode(12,OUTPUT); //12=MISO pas fait par SPI.begin
   pinMode(11,INPUT); //11=MOSI pas fait par SPI.begin
   process = false;
   sei();
   
}

ISR (SPI_STC_vect) // SPI interrupt routine 
{ 
   c = SPDR; // read byte from SPI Data Register
   process = true;
}

void loop()
{
  if (process) {
    cmpt++;
    process = false;
    SPDR = cmpt;
  }
}

Tout cela fonctionne correctement sans grande difficultés. Les difficultés arrivent quand on veut transmettre des données plus grandes que l'octet. C'est à ce sujet que nous allons nous intéresser maintenant.

Transmissions de données de plusieurs octets[modifier | modifier le wikicode]

Problème de synchronisation entre un maître et un esclave

Nous restons dans les conditions particulières que nous avons eu à affronter en robotique mobile : c'est l'esclave qui a des données à transmettre au maître, mais ceci se fait à l'initiative du maître. Le problème à résoudre dans ce cas est un problème de synchronisation. Si l'on veut que l'esclave donne ses données les plus récentes il lui faut un moyen pour savoir s'il a à livrer l'octet de poids fort ou l'octet de poids faible. Si l'on se contente de livrer une fois sur deux l'octet de poids faible et l'octet de poids fort il est certain que vous aurez des problèmes comme le montre la figure. Pour bien comprendre le problème il vous faut remarquer plusieurs choses :

  • le maître exécute sa vie. C'est lui qui décide quand il va envoyer les deux interruptions pour lire les données des capteurs.
  • le temps entre les deux interruptions pour lecture de données et les prochaine n'est pas du tout égal. Cela peut être 10 ms contre 0.2 ms soit un rapport de 50
  • l'écriture SPR = quelque chose dans les interruptions n'est pris en compte que l'interruption suivante ! Et c'est là qu'est le problème !

Si vous réalisez ce qui est montré dans la figure, les poids faible et fort de la variable pourront donc avoir 10 ms d'écart. Si les données capteurs ne varient pas beaucoup cela peut n'avoir que des conséquences négligeables. Mais si les données varient rapidement les conséquences peuvent être désastreuses.

Pour commencer, nous allons quand même nous intéresser au cas de données 16 bits en essayant de résoudre le problème de synchronisation tout juste évoqué.

Cas d'une donnée 16 bits[modifier | modifier le wikicode]

Synchronisation entre un maître et un esclave avec 3 lectures

Pour réaliser la synchronisation, le maître fait trois écritures pour récupérer deux octets. C'est ce que montre la figure ci-contre. La première interruption permet d'écrire le poids faible dans SPR qui sera lu par la deuxième lecture qui écrit le poids fort dans SPR qui sera pris en compte dans la troisième lecture.

Nous avons découvert qu'il faut un délai minimum entre les écritures SPI pour un fonctionnement correct. Il est probable que la raison en soit l'attente de la fin de la réalisation de l'interruption sur l'esclave avant d'envoyer d'autres données... sans quoi l'interruption suivante peut ne pas se réaliser.

  • SPI maître pour transmettre 2 octets :
#include <SPI.h>
#define START 128
const int slaveSelectPin = 10; // ou autre
uint16_t data=0;// donnée a transmettre
void setup() {
  // set the slaveSelectPin as an output:
  Serial.begin(9600);
  pinMode(slaveSelectPin, OUTPUT);
  // initialize SPI:
  SPI.begin();
}

void loop() {
  uint8_t dataSlave;
  digitalWrite(slaveSelectPin, LOW);
  dataSlave=SPI.transfer(START);
  //digitalWrite(slaveSelectPin, HIGH);
  data=0;
  //digitalWrite(slaveSelectPin, LOW);
  delayMicroseconds(200);
  dataSlave=SPI.transfer(8);
  //digitalWrite(slaveSelectPin, HIGH);
  data = dataSlave;
  data <<= 8;
  //digitalWrite(slaveSelectPin, LOW);
  delayMicroseconds(200);
  dataSlave=SPI.transfer(0);
  data += dataSlave;
  digitalWrite(slaveSelectPin, HIGH);
  Serial.println(data);
  delay(1000);
}


  • SPI esclave pour transmettre 2 octets :
#include <SPI.h>
const int slaveSelectPin = 10;

volatile boolean process;
uint16_t cmpt=0;
volatile uint8_t c=0;

void setup() {
  Serial.begin(9600);
  // set the slaveSelectPin as an output:
  pinMode(slaveSelectPin, INPUT);
  // initialize SPI:
  SPI.begin();
  // slave select
   //SPCR |= (1<<SPE)|(1<<SPIE);
   SPCR &= ~(1<<MSTR); // turn on SPI in slave mode
   SPI.attachInterrupt(); // turn on interrupt
   pinMode(12,OUTPUT); //12=MISO pas fait par SPI.begin
   pinMode(11,INPUT); //11=MOSI pas fait par SPI.begin
   process = false;
   sei();
   
}

ISR (SPI_STC_vect) // SPI interrupt routine 
{ 
   c = SPDR; // read byte from SPI Data Register
   
   if (c==128) SPDR = (cmpt >> 8); else
     if (c==8) SPDR = (cmpt); else
       SPDR = 0xFF;
   process = true;
}

void loop()
{
  //cmpt++;
  //delay(500);
  
  if (process) {
    cli(); // Ne pas déranger
    cmpt++;
    sei();
    process = false;
    //SPDR = cmpt;
    Serial.println(c);
  } 
  //SPDR = 10;
}

Cas d'une donnée de type float[modifier | modifier le wikicode]

Les données de type float sont sur 32 bits (4 octets) sur à peu près toutes les architectures. C'est le cas sur les AVR avec le compilateur avrgcc.

Pour réaliser la synchronisation nous allons utiliser aussi une écriture supplémentaire à savoir ici 5 lectures par le maître.

  • Esclave Float
#include <SPI.h>
const int slaveSelectPin = 10;

volatile boolean process;
uint16_t cmpt=0;
volatile uint8_t c=0;

union {
     float f_temp;
     uint32_t temp;
  } Bx;

void setup() {
  //Serial.begin(9600);
  // set the slaveSelectPin as an output:
  pinMode(slaveSelectPin, INPUT);
  // initialize SPI:
  SPI.begin();
  // slave select
   //SPCR |= (1<<SPE)|(1<<SPIE);
   SPCR &= ~(1<<MSTR); // turn on SPI in slave mode
   SPI.attachInterrupt(); // turn on interrupt
   pinMode(12,OUTPUT); //12=MISO pas fait par SPI.begin
   pinMode(11,INPUT); //11=MOSI pas fait par SPI.begin
   process = false;
   sei();
   Bx.f_temp=0.0;
   
}

ISR (SPI_STC_vect) // SPI interrupt routine 
{ 
   c = SPDR; // read byte from SPI Data Register
   
   if (c==0) SPDR = (Bx.temp >> 24); else
     if (c==1) SPDR = (Bx.temp >> 16); else
       if (c==2) SPDR = (Bx.temp >> 8); else
          if (c==3) SPDR = (Bx.temp); else
       SPDR = 0xFF;
   process = true;
}

void loop()
{
  //cmpt++;
  //delay(500);
  
  if (process) {
    cli(); // en aucun cas être dérangé !!!!
    Bx.f_temp += 0.1;
    sei();
    process = false;
    //SPDR = cmpt;
    //Serial.println(c);
  } 
  //SPDR = 10;
}
  • Maître float :
#include <SPI.h>
#define START 0
const int slaveSelectPin = 10;
uint16_t data=0;
union {
     float f_temp;
     uint32_t temp;
  } Bx;

void setup() {
  // set the slaveSelectPin as an output:
  Serial.begin(9600);
  pinMode(slaveSelectPin, OUTPUT);
  // initialize SPI:
  SPI.begin();
}

void loop() {
  uint8_t dataSlave;
  digitalWrite(slaveSelectPin, LOW);
  dataSlave=SPI.transfer(START);
  //digitalWrite(slaveSelectPin, HIGH);
  Bx.f_temp=0.0;
  //digitalWrite(slaveSelectPin, LOW);
  delayMicroseconds(200);
  //delay(1);
  dataSlave=SPI.transfer(1);
  //digitalWrite(slaveSelectPin, HIGH);
  Bx.temp += dataSlave;
  Bx.temp <<= 8;
  //digitalWrite(slaveSelectPin, LOW);
  delayMicroseconds(200);
  //delay(1);
  dataSlave=SPI.transfer(2);
  Bx.temp += dataSlave;
  Bx.temp <<= 8;
  delayMicroseconds(200);
  //delay(1);
  dataSlave=SPI.transfer(3);
  //digitalWrite(slaveSelectPin, HIGH);
  Bx.temp += dataSlave;
  Bx.temp <<= 8;
  //digitalWrite(slaveSelectPin, LOW);
  delayMicroseconds(200);
  //delay(1);
  dataSlave=SPI.transfer(4);
  //digitalWrite(slaveSelectPin, HIGH);
  Bx.temp += dataSlave;
  //digitalWrite(slaveSelectPin, LOW);
  digitalWrite(slaveSelectPin, HIGH);
  Serial.println(Bx.f_temp);
  delay(1000);
}

Pour pouvoir utiliser correctement le découpage des flottants en octets on a élé obligé d'utiliser une union qui permet de superposer deux types différents sur une même donnée. Par exemple quand on travaille sur le flottant on utilise le champ .f_temp et quand on veut réaliser des décalages on utilise le champ .temp

Encore de la synchronisation[modifier | modifier le wikicode]

Nous avons montré comment résoudre le problème de synchronisation des données entre un esclave et un maître. Mais tous les problèmes sont-ils résolus pour autant ?

La réponse est négative malheureusement. Pourquoi ? A cause de l'interruption dans l'esclave !

En effet l'esclave a pour objectif de lire des capteurs et éventuellement de faire des calculs (qui peuvent être compliqués pour un accéléromètre). Une fois ces calculs réalisé, le programme mettra à jour la variable qui sera découpée en tranche par l'interruption. Que se passe-t-il si l'interruption a lieu en pleine mis à jour de cette variable ? Cela peut être aussi dramatique que ce que l'on a cherché à résoudre avec une lecture supplémentaire des données SPI. La seule différence est qu'il y en a une qui peut arriver systématiquement (c'est pour cela qu'on a résolu le problème) et l'autre qui n'a qu'une petite probabilité d'arriver. En informatique théorique, on chercherait à résoudre ce problème même si la probabilité est très faible. Pour nous, que dire, on prend le risque.

Voici cependant le résultat donné par notre programme de transmission d'un flottant :

9242.57
9243.07
9243.57
9244.06
9244.56
9245.06
9245.56
9246.06
9216.50
9247.20
9247.70
9248.20
8256.50
8256.50

On y distingue quelques dysfonctionnements en particulier les deux dernières lignes ! On attend en effet un compteur qui compte de 0.5 en 0.5. Mais nous ne savons pas dire pour le moment si cela est dû au problème évoqué dans cette section ou à un autre. Il nous faudra investiguer plus loin plus tard.


Pour aller plus loin sur le domaine, lire Parallélisme Informatique dans Wikipédia.

Complément : utiliser un compilateur C avec une carte Arduino[modifier | modifier le wikicode]

Nous allons examiner comment compiler un programme C et le télécharger dans une carte Arduino. Nous supposerons que l'environnement Arduino est installé et que l’on travaille sous Linux. Ceci nous permet de supposer que le compilateur C avr-gcc est correctement installé ainsi que le logiciel de téléchargement avrdude.

Pour simplifier nous donnons directement un script bash capable de réaliser l’ensemble :

#!/bin/bash
avr-gcc -g -mmcu=atmega2560 -Wall -Os -c Mega2560Demo1.c 
avr-gcc -g -mmcu=atmega2560 -o Mega2560Demo1.elf -Wl,-Map,Mega2560Demo1.map Mega2560Demo1.o 
#avr-objdump -h -S hello.elf > hello.lss
#avr-objcopy -O binary -R .eeprom hello.elf hello.bin
avr-objcopy -R .eeprom -O ihex Mega2560Demo1.elf Mega2560Demo1.hex

/usr/share/arduino/hardware/tools/avrdude -C/usr/share/arduino/hardware/tools/avrdude.conf -v -v -v -v -patmega2560 -cwiring -P/dev/ttyACM0 -b115200 -D -V -Uflash:w:Mega2560Demo1.hex:i

Ce script fonctionne pour un Arduino MEGA2560 et vous devrez l'adapter en changeant le processeur pour les autres cartes.

Voici un programme d'essai pour la carte MEGA2560 :

//**** OK : Arduino MEGA2560 + shield
#include <avr/io.h> 
main(void) 
{ 
  DDRB = 0xFF; // 8 sorties pour B 
  DDRE = 0x00; // 8 entrées pour E
  while(1) 
    PORTB = PINE; // recopie du PORTE dans le PORTB qui allume les LEDs	 
}

Ce programme fonctionne correctement mais nécessite un shield qui est décrit ICI (WIKI interne à GEII Troyes)

Exercice 1[modifier | modifier le wikicode]

Les LEDs du shield présenté dans la section précédente sont reliées aux broches 6 à 13 de l'arduino MEGA2560. Une lecture de son schéma fait apparaître la correspondance :

Numéro f5 f4 f3 f2 f1 f0 p1 p0
Couleur r o v r o v v r
Arduino Pin 13 12 11 10 9 8 7 6
Port PB7 PB6 PB5 PB4 PH6 PH5 PH4 PH3

Réaliser en C un programme réalisant un chenillard simple.

Solution de l'exercice 1[modifier | modifier le wikicode]

Voici le programme :

//**** OK : Arduino MEGA2560 + shield
#include <avr/io.h> 
#undef F_CPU
#define F_CPU 16000000UL
#include <util/delay.h>
void afficheLeds(unsigned char ch){
  unsigned char ch_partie;
  ch_partie = (ch << 3) & 0x78;
  PORTH = ch_partie;
  ch_partie = ch & 0xF0;
  PORTB = ch_partie;
}
void main(void) 
{ 
  unsigned char ch,i;
  DDRH = 0x78; // 4 sorties pour H 
  DDRB = 0xF0; // 4 sorties pour B
  while(1) {
    ch = 1;
    for (i=0;i<8;i++) {
       afficheLeds(ch);
       _delay_ms(1000);
       ch <<=1;
    }
  } 
}


Exercice 2[modifier | modifier le wikicode]

Mettre en œuvre le timer0 sur le MEGA2560 avec un prescaler à 1024 et une attente active du bit T0V0 du registre TIFR0. On basculera le bit b4 du PORTB qui est relié à une LED. Quelle est la fréquence de basculement du bit b4 si la fréquence du quartz est 16 MHz ?

Solution de l'exercice 2[modifier | modifier le wikicode]

Le nom des registres est un peu changé par rapport à un ATMega8.

//**** OK : Arduino MEGA2560 + shield
#include <avr/io.h> 
void main(void){
 // initialisation du timer   division par 1024 
         TCCR0B = 0x05; // prescaler 1024 , entrée sur quartz 
         TCNT0 = 0x00; //   tmr0  : début du comptage dans 2 cycles 
 // bit RB4 du PORTB en sortie
         DDRB |= 0x10; //RB4 as output
         while(1) {
                 TIFR0 |= 0x01; // clr TOV0 with 1
                 while ((TIFR0 & (0x01<<TOV0)) == 0);
                 PORTB ^= 0x10; // on bascule avec ou exclusif
                 TIFR0 &= ~(1 << TOV0); // reset the overflow flag 
         }
}

Fréquence 16 000 000 /(1024*256*2) = 32 Hz

D'après ce résultat une division par 32 doit donner 1 Hz. On le fait par exemple par interruption, toujours avec un MEGA2560 :

//**** OK : Arduino MEGA2560 + shield
#include <avr/io.h> 
#include <avr/interrupt.h>
// compteur
volatile unsigned char cpt=0;

// Fonction de traitement Timer 0 OverFlow 
ISR(TIMER0_OVF_vect){ 
      cpt++; 
      if(cpt==31) { 
            PORTB ^=(1<<PB4); 
            cpt=0; 
      } 
} 

void main(){ 
// IT Timer0 Over Flow Active 
 TIMSK0=(1<<TOIE0);
// Prescaler 1024 (Clock/1024) 
  TCCR0B = (1<<CS02) | (1<<CS00);
//Configuration PORTB.4 en sortie 
  DDRB |= (1<<DDB4);   
  PORTB &= ~(1<<PB4); // PORTB.4 <-0 
//activation des IT (SREG.7=1) 
// sei(); 
  SREG |= 0x80; // equivalent à sei()
  while(1);
}

Nous n'avons pas utilisé "sei()" mais il est naturellement possible de le faire.

Améliorations[modifier | modifier le wikicode]

Un makefile serait probablement plus adapté à la situation car ici une erreur de compilation chargera le fichier HEX (qui sera de ce fait un fichier plus ancien).

Pour commencer nous donnons un makefile trouvé sur Internet qu’il nous faudra essayer, adapter et éventuellement traduire :

# tools
AVRDUDE=avrdude -F -V
OBJCOPY=avr-objcopy
CC=avr-gcc
RM=rm -f
 
# parameters
MCU=atmega328
F_CPU=16000000UL
BIN_FORMAT=ihex
PORT=/dev/cuaU0
BAUD=57600
PROTOCOL=arduino
PART=ATMEGA3290
CFLAGS=-Wall -Os -DF_CPU=$(F_CPU) -mmcu=$(MCU)
PROG=addr
SUDO=sudo
 
.SUFFIXES: .elf .hex
 
.c.elf:
    $(CC) $(CFLAGS) -o $@ $<
 
.elf.hex:
    $(OBJCOPY) -O $(BIN_FORMAT) -R .eeprom $< $@
 
.PHONY: all
all: ${PROG}.hex
 
${PROG}.hex: ${PROG}.elf
 
${PROG}.elf: ${PROG}.c
 
.PHONY: clean
clean:
    $(RM) ${PROG}.elf ${PROG}.hex
 
.PHONY: upload
upload: ${PROG}.hex
    ${SUDO} $(AVRDUDE) -c $(PROTOCOL) -p $(PART) -P $(PORT) \
        -b $(BAUD) -U flash:w:${PROG}.hex

Rester dans l'environnement Arduino pour faire du C[modifier | modifier le wikicode]

Nous avons découvert qu’à priori on peut écrire du C dans l'environnement Arduino. Nous voulons dire un programme avec un main() et sans setup() ni loop(). Ceci se fait dans l'éditeur Arduino et la compilation comme le téléversement se fait comme pour un sketch Arduino. C'est pratique cela évite de lancer "avrdude" en ligne de commande par exemple et de trop se poser des questions sur les commandes à réaliser.

Début d’un principe
Fin du principe


Voici un exemple qui a été testé sur une carte UNO.

Création de 1 kHz sur PB3 par attente passive du drapeau de débordement du timer 2[modifier | modifier le wikicode]

Pour information PB3 est repéré par l'entrée/sortie numéro 11 sur la platine UNO comme le montre le schéma officiel.

On choisit une division par 128, qui demande alors un comptage de 62,5 arrondi ici à 63. Ce programme a été testé correct avec un oscilloscope sur une carte UNO et a été écrit tel quel dans l'environnement Arduino.

//*************** Testé OK pour UNO
// le #include <avr/io.h> habituel est complètement inutile
int main() {
  // variables
  // e/S
  DDRB |= (1<<PB3);
// configuration du timer2
  TCCR2A &= ~(1<<WGM20);
  TCCR2A &= ~(1<<WGM21);
  TCCR2B &= ~(1<<WGM22);
// prescaler à (101) soit division par 128
  TCCR2B |= (1<<CS22);
  TCCR2B &= ~(1<<CS21);
  TCCR2B |= (1<<CS20);
  TIFR2 |= (1<<TOV2);
  TCNT2=0;
  for (;;) {
// Attendre un débordement du timer
    while (!(TIFR2 & (1<<TOV2))) ;
// Acquitter le débordement
    TIFR2 |= (1<<TOV2);
    TCNT2 += 193; //256-63 on peut aussi utiliser TCNT2 = 193;
// Changer l'état de la sortie
    PORTB ^= (1<<PB3);
  }
}

La valeur 193 peut être ajustée pour être le plus proche possible de 1kHz.

Voir aussi[modifier | modifier le wikicode]