Micro contrôleurs AVR/Les communications en tout genre

Début de la boite de navigation du chapitre

Nous avons l'intention dans ce chapitre d’aborder divers types de communication : série asynchrone, série synchrone, I2C et USB.

Les communications en tout genre
Icône de la faculté
Chapitre no 7
Leçon : Micro contrôleurs AVR
Chap. préc. :Le Timer 2
Chap. suiv. :De la programmation des AVRs aux bootloaders
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Micro contrôleurs AVR : Les communications en tout genre
Micro contrôleurs AVR/Les communications en tout genre
 », n'a pu être restituée correctement ci-dessus.

Communication série asynchrone ou RS232

modifier

La partie du circuit spécialisée gèrant la liaison série (ou RS232) s’appelle une UART.

 
Trame RS232

On rappelle que ce type de liaison série permet d'envoyer des informations sans horloge de référence. Elle est à un logique au repos. Une trame est composée des 8 bits à transmettre, précédé d'un bit de start ("0" logique), et suivi par au moins un bit de stop ("1" logique). Le bit de stop peut être doublé et éventuellement précédé par un bit de parité. Pour une parité paire, le bit de parité est mis à "0" quand le nombre de "1" est pair tandis qu'une parité impaire met à "0" pour un nombre impair de "1".

La vue de l'image ci-contre semble indiquer le contraire de ce que je suis en train d'expliquer. Mais n'oubliez pas que du point de vue électrique, un niveau logique "0" est représenté par une tension de +3 V à +25 V et un niveau logique "1" par une tension de −3 V à −25 V (codage NRZ). Ordinairement, des niveaux de +12 V et −12 V sont utilisés.

Comme nous allons utiliser pour nos essais des platines Arduino pour lesquelles la liaison série est réalisée à l'aide de l’USB, les problèmes de tension n'ont pas lieu.

La liaison série est très populaire à cause de son évolution justement avec l'USB et surtout que tous les systèmes d'exploitation proposent un logiciel pour communiquer par la liaison série. Il est appelé hyperterminal sous Windows. Même si nous utilisons essentiellement Linux pour nos essais, nous continuerons à l'appeler hyperTerminal.

Voici quelques brochages utiles qu’il est facile de retrouver sur Internet.

rs232 basse tension RXD TXD
UNO PD0(Arduino:0) PD1 (Arduino:1)
LEONARDO PD2 (Arduino:0) PD3 (Arduino:1)
Pro Micro (Sparkfun) PD2 (Arduino:0) PD3 (Arduino:1)

Les registres associés

modifier

Le « n » apparaissant dans ces dessins peut prendre la valeur 0 ou 1 dans l'ATMega328.

 
USART and ATMega328

Les bits du registre UCSRnA sont :

  • RXCn : réception complète
  • TXCn : transmission complète
  • UDREn : USART Data Register Empty (vide)
  • FEn : Frame Error
  • DORn : Data OverRun
  • UPEn : USART Parity Error
  • U2Xn : est expliqué dans le dessin
  • MPCMn : Multi Processor Communication Mode

Les bits du registre UCSRnB sont :

  • RXCIEn : interruption quand réception complète autorisée
  • TXCIEn : interruption quand transmission complète autorisée
  • UDRIEn : interruption quand UDRn est vide autorisée
  • RXENn : autorisation de la réception
  • TXENn : autorisation de la transmission
  • UCSZn2 : taille de caractère quand combiné avec UCSZn1:0
  • RXB8n : 9° bit de réception au cas où ce mode est choisi
  • TXB8n : 9° bit de transmission au cas où ce mode est choisi

Une librairie d'utilisation

modifier

Nous avons trouvé sur Internet la librairie suivante qui a été écrite pour un ATMega168. Nous l'avons fait fonctionner telle quelle sur un ATMega2560 et la platine Arduino correspondante. Nous la publions avec quelques modifications.

 /*
Title:		SerialCom.c 
Date Created: 6/9/2009
Last Modified: 6/9/2009
Target:		Atmel ATmega168, ATmega368
Environment:	AVR-GCC 
  Note: the makefile is expecting a '168 with a 16 MHz crystal.
Adapted from the Arduino sketch "Serial Call and Response," by Tom Igoe.
  //  This program sends an ASCII A (byte of value 65) on startup
  //  and repeats that until it gets some data in.
  //  Then it waits for a byte in the serial port, and 
  //  sends three (faked) sensor values whenever it gets a byte in.
Written by Windell Oskay, http://www.evilmadscientist.com/
Copyright 2009 Windell H. Oskay
Distributed under the terms of the GNU General Public License, please see below.
Additional license terms may be available; please contact us for more information.
More information about this project is at 
http://www.evilmadscientist.com/article.php/peggy
-------------------------------------------------
USAGE: How to compile and install
A makefile is provided to compile and install this program using AVR-GCC and avrdude.

To use it, follow these steps:
1. Update the header of the makefile as needed to reflect the type of AVR programmer that you use.
2. Open a terminal window and move into the directory with this file and the makefile. 
3. At the terminal enter
		make clean <return>
		make all <return>
		make program <return>
4. Make sure that avrdude does not report any errors. If all goes well, the last few lines output by avrdude
should look something like this:

avrdude: verifying ...
avrdude: XXXX bytes of flash verified
avrdude: safemode: lfuse reads as E2
avrdude: safemode: hfuse reads as D9
avrdude: safemode: efuse reads as FF
avrdude: safemode: Fuses OK
avrdude done. Thank you.
If you a different programming environment, make sure that you copy over 
the fuse settings from the makefile.
-------------------------------------------------
This code should be relatively straightforward, so not much documentation is provided. If you'd like to ask 
questions, suggest improvements, or report success, please use the evilmadscientist forum:
http://www.evilmadscientist.com/forum/
-------------------------------------------------
 This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <avr/io.h> 

#define F_CPU 16000000	// 16 MHz oscillator.
#define BaudRate 9600
#define MYUBRR (F_CPU / 16 / BaudRate ) - 1 

unsigned char serialCheckRxComplete(void)
{
	return( UCSR0A & _BV(RXC0)) ;		// nonzero if serial data is available to read.
}

unsigned char serialCheckTxReady(void)
{
	return( UCSR0A & _BV(UDRE0) ) ;		// nonzero if transmit register is ready to receive new data.
}

unsigned char serialRead(void)
{
	while (serialCheckRxComplete() == 0)		// While data is NOT available to read
	{;;} 
	return UDR0;
}

void serialWrite(unsigned char DataOut)
{
	while (serialCheckTxReady() == 0)		// while NOT ready to transmit 
	{;;} 
	UDR0 = DataOut;
}

void serialInit(void) {
  //Serial Initialization
 	/*Set baud rate 9600 */ 
	UBRR0H = (unsigned char)(MYUBRR>>8); 
	UBRR0L = (unsigned char) MYUBRR; 
	/* Enable receiver and transmitter */
	UCSR0B = (1<<RXEN0)|(1<<TXEN0); 
	/* Frame format: 8data, No parity, 1stop bit */ 
	UCSR0C = (3<<UCSZ00);	
}

Cette librairie a été utilisée avec succès avec une platine Arduino UNO et MEGA2560. L'horloge est aussi à 16 MHz comme ce qui est supposé par cette librairie. L'exercice 8 ici fait une lecture d'une valeur supposée être fournie par deux caractères, valeur variant entre 00 et FF, pour régler le rapport cyclique.

Exercices

modifier

Un exercice a déjà été proposé dans ce livre : PWM rapide dans le chapitre sur le timer 0.

En voici un autre assez proche.

Travail à réaliser en C

modifier

Pour cette partie, on utilise l'IDE Arduino mais le langage C pur (avec donc un main()).

Changer la vitesse de transmission
modifier

La librairie présentée fonctionne pour une vitesse de transmission de 9600 bauds. Cette vitesse est choisie avec les registres UBRR0H et UBRR0L comme indiqué dans les commentaires.

Modifier la librairie pour la faire fonctionner à 19200 bauds. Écrire un programme complet qui permet de réaliser un test avec cette nouvelle vitesse dans l'hyperterminal Arduino.

Après cette mise en jambes, venons-en aux problèmes sérieux...

Changer la fréquence sur un buzzer
modifier

On désire changer la fréquence sonore d'un buzzer à l'aide du mode CTC du timer 2. La valeur envoyée pour déterminer la fréquence sera systématiquement envoyée par la liaison série sous forme de deux caractères 0,...,9,A,...,F. Dans ce genre de problème, la difficulté est de comprendre la différence entre des caractères et des valeurs.

  • Écrire un sous-programme "void usart_puts_hexa(unsigned char val)" destiné à transformer la valeur val (hexadécimale) en deux caractères affichables (deux parcequ'on est sur 8 bits). Tester avec un programme principal.

Nous allons maintenant réaliser l'opération inverse : lire deux caractères (en représentation hexadécimale) qui arrivent par la liaison série et les transformer en une valeur numérique correspondante sur un octet.

  • Un sous-programme "unsigned char usart_gets_hexa()" sera donc chargé de lire ces deux caractères d’en vérifier la syntaxe et d’en retourner la valeur. Écrire ce sous-programme et le tester avec le sous-programme de la question précédente. On affichera "--" en cas d'erreur de syntaxe. TOUT SE PASSE PAR LA LIAISON SERIE. Pouvez-vous modifier la fonction un peu naïve ci-dessous pour qu'elle gère correctement les erreurs (car elle ne le fait pas !!!!) même si elle fonctionne parfaitement :
// Pour comprendre la faiblesse de cette fonction donnez-lui "GG" à convertir
// elle vous retourne quelque chose alors que "GG" n'est manifestement pas un
// nombre hexadécimal
unsigned char usart_gets_hexa() {
  unsigned char val;
  char tab[3];
  tab[0] = serialRead(); //poids fort
  tab[1] = serialRead(); //poids faible
  while (serialCheckRxComplete()) serialRead(); // on vide buffer
  tab[0] -= '0';
  tab[1] -= '0';
  if (tab[0] > 9) tab[0] -= 7;
  if (tab[1] > 9) tab[1] -= 7;
  val = (tab[0] << 4) + tab[1];  
  return val;
}

Et l’apothéose finale ....

  • Changer votre programme en utilisant les sous-programmes développés pour la question précédente pour qu’il réalise une fréquence audible sur le bit OC0A du PORTB variant en fonction de la valeur reçue (par la liaison série).

Le bit OC0A correspond au bit B6 pour l'ATMega328 de l'Arduino UNO.

Exercice 2 : Commande d'un servomoteur

modifier
 
Schéma du matériel utilisé

Un potentiomètre est branché sur un Arduino sur l'entrée A0. Un servomoteur est branché sur un deuxième Arduino (sortie (9). Les deux Arduinos sont reliés entre eux par une liaison série. Écrire les deux programmes en C pour les deux Arduinos pour que le potentiomètre commande le servomoteur par l'intermédiaire de la liaison série.

Indications :

  • la résolution de ce problème nécessite quelques connaissances sur la conversion analogique numérique. La broche A0 dont il est question ici est l'entrée AN0. Ceci peut sembler être une information redondante mais elle est seulement vraie pour les Arduino UNO (pas LEONARDO par exemple).
  • La commande d'un servomoteur par un potentiomètre est étudiée dans le chapitre sur la conversion analogique numérique.
  • La broche (9) de l'Arduino est PD1. Mais elle est aussi OC1A. Cela signifie clairement que le timer que l'on utilisera pour commander le servomoteur est le timer1
  • La commande d'un servomoteur par le timer1 a déjà été traité comme exercice 3 dans le chapitre sur le TIMER1

Cas particulier de l'Arduino Leonardo

modifier

La particularité de l'Arduino Leonardo est la gestion de la liaison série par l'USB.

Nous allons utiliser du code du site pjrc.com : USB serial

Voir aussi WIKI IUT Troyes

Communication série synchrone ou SPI

modifier

Un article de wikipédia Serial Peripheral Interface présente les communications SPI qui nous intéressent dans cette section.

Interface SPI décrit des programmes que nous reprendrons.

Description du protocole

modifier

Le protocole SPI est synchrone : une horloge synchronise l'échange de données. Dans tout échange on définit un Maître qui réalise l'horloge et un Esclave. L'esclave (il peut y en avoir plusieurs) est choisi avec une entrée spéciale appelée SS (Slave Select). A ce point, nous connaissons deux broches du SPI : l'horloge appelée SCK et SS. Il en manque deux pour être complet :

  • MISO : Master in Slave Out : un des esclaves envoie des données au maître
  • MOSI : Master Out Slave In : le maître envoie des données à un des esclaves qu'il a choisi avec le Slave Select)

Ces deux broches laissent entendre un échange full duplex (complet dans les deux sens). Voici donc comment les choses se passent avec un Maître et un Esclave :

 
Liaison SPI: un maître et un esclave

Ce schéma ne représente que le protocole électrique. En général il y a un protocole logique au-dessus, c'est-à-dire un moyen pour le maître de demander certaines données et pas d'autres à l'esclave. Cela se fait avec une série d'octets prévue par le constructeur de l'esclave. Il vous faudra donc lire la documentation de l'esclave (en général un capteur) pour connaître ce protocole.

Si vous avez plusieurs esclaves une configuration de ce genre est à choisir :

 
Liaison SPI avec un maître et trois esclaves

Comme nous allons utiliser des cartes Arduino pour nos expérimentations, nous allons commencer par donner leurs brochages.

Quelques brochages pour les cartes Arduino UNO, Leonardo et Mega2560

modifier

Voici résumé dans un tableau le brochage des cartes qui nous intéressent :

SPI MISO MOSI SCK SS
UNO / NANO PB4 (Arduino:12) PB3 (Arduino:11) PB5 (Arduino:13) PB2 (Arduino:10)
LEONARDO PB3 (ICSP:1) PB2 (ICSP:4) PB1 (ICSP:3) --aucune--
MEGA2560 PB3 (Arduino:50) PB2 (Arduino:51) PB1 (Arduino:52) PB0 (Arduino:53)
Pro Micro (Sparkfun) PB3 (Arduino:14) PB2 (Arduino:16) PB1 (Arduino:15) --aucune--

Quel est l'impact sur le Leonardo du manque de broche dédiée à SS ? En principe aucun parce que cette broche SS n'est utile que si l'on désire utiliser le Leonardo en esclave. Vous pouvez utiliser n'importe quel autre bit de port pour sélectionner des esclaves. Mais il faudra les gérer par vous-même.

Particularité du Leonardo

modifier

Comme on peut le voir dans ce tableau, les broches utilisées pour SPI sur la Leonardo ne sont pas reliées aux connecteurs mais directement sur le programmateur ICSP. Pour éviter des recherches sur internet, nous rappelons la connectique associée des six broches avec les deux photos ci-contre.

 
Connectique SPI

Voici par exemple ci-contre la connectique ICSP présente sur une carte Arduino pour laquelle MISO et MOSI sont inversés par rapport à la figure ci-dessus.

 
Connectique ISP de l'Arduino avrc MISO en (1)

Mise en œuvre matérielle

modifier
 
Chronogramme des différentes configurations d'horloge

Le problème de la polarité d'horloge est important : un maître et un esclave peuvent être incapabes de se comprendre s'ils n'ont pas la même polarité. Le dessin de wikipédia est redonné ici comme référence.

Les registres correspondants à la description matérielle sont maintenant présentés et seront suivi par un schéma de l'architecture correspondante. En ce qui concerne le registre SPCR :

  • SPIE: pour autoriser les interruptions SPI
  • SPE: SPI Enable, doit être positionné à 1 pour toute opération SPI
  • DORD: Data Order, 1 pour une transmissin du poids faible en premier
  • MSTR: Master/Slave Select, 1 pour le positionnement en maître. Ce bit nécessite d’être en accord avec SS (Slave Select)
  • CPOL: Clock Polarity, 1 pour SCK haut quand idle, 0 pour SCK bas quand idle.
  • CPHA: Clock Phase, détermine si les données sont échantillonnées sur le premier front ou le deuxième front de SCK.
 
SPI registers in AVR

Pour le registre SPSR :

  • SPIF: est le drapeau d'interruption
  • WCOL: Write COLlision Flag

Le reste est donné dans le dessin.

Travail en C sur le SPI

modifier

Il est facile de trouver un programme d'exemple en C sur internet. En voici un exemple serial peripheral interface in avr microcontrollers :

//SPI master
#include <avr/io.h>
#include <util/delay.h>
//SPI initvoid
void SPIMasterInit(void) {
//set MOSI, SCK and SS as output
  DDRB |= (1<<PB3)|(1<<PB5)|(1<<PB2);
//set SS to high
  PORTB |= (1<<PB2);
//enable master SPI at clock rate Fck/16
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
}
//master send function
void SPIMasterSend(uint8_t data){
//select slave
  PORTB &= ~(1<<PB2);
//send data
  SPDR=data;
//wait for transmition complete
  while (!(SPSR &(1<<SPIF)));
//SS to high
  PORTB |= (1<<PB2);
}

int main(void) {
//initialize master SPI
  SPIMasterInit();
//initial PWM value
  uint8_t pwmval = 0;
  while (1) {
    SPIMasterSend(pwmval++);
    _delay_ms(1000);
  }
}

Trouvez un esclave SPI et essayez ce programme. Si aucun esclave SPI n'est disponible, réalisez-en un avec une autre platine Arduino.

//SPI slave
#include <avr/io.h>
#include <avr/interrupt.h>
//SPI init
void SPISlaveInit(void) {
//set MISO as output
  DDRB |= (1<<PB4);
//enable SPI and enable SPI interrupt
  SPCR = (1<<SPE)|(1<<SPIE);
}
void InitPort(void) {
//set PD6 (OC0A) as output
  DDRD|=(1<<PD6);
}
//Initialize Timer0
void InitTimer0(void) {
//Set Initial Timer value
  TCNT0=0;
//Place compare value to Output compare register
  OCR0A=0;
//Set fast PWM mode
//and make clear OC0A on compare match
  TCCR0A|=(1<<COM0A1)|(1<<WGM01)|(1<<WGM00);
}
void StartTimer0(void) {
//Set prescaller 64 and start timer
  TCCR0B|=(1<<CS01)|(1<<CS00);
}
ISR(SPI_STC_vect) {
  OCR0A=SPDR;
}

int main(void) {
//initialize slave SPI
  SPISlaveInit();
  InitPort();
  InitTimer0();
  StartTimer0();
  sei();
  while (1) {
    //loop
  }
  return 0;
}

On ne vous demande pas de comprendre la partie interruption SPI de ce programme. Seule la partie Timer0 est à comprendre puisqu’il faut correctement brancher une LED dont la luminosité doit changer en fonction de ce qui est envoyé par le SPI.

Exercice 2

modifier

En vous aidant de la documentation du brochage des quelques Arduino plus haut, on vous demande de :

1°) modifier le programme ci-dessus (uniquement void SPISlaveInit(void)) pour qu’il fonctionne correctement avec un MEGA2560 avec une horloge SPI égale à la fréquence système divisée par 8.

2°) trouver le mode de fonctionnement correspondant ?

3°) Étant pointilleux sur les principes, on vous demande de modifier aussi le sous-programme de réception, sachant que SPIF est un drapeau comme son nom l'indique.

Lecture des données provenant d'une caméra Pixy2

modifier

La caméra Pixy2 est très utile si vous voulez faire des challenges de robotique mobile. Elle est capable d'apprendre à reconnaître des objets, de les traquer et surtout de fournir un certain nombre de données sur le position et la taille des objets reconnus. Par exemple, dans la coupe de France des IUT GEII, le problème est de reconnaître des balles de tennis et de les mettre du côté adverse. C'est typiquement ce qui peut être fait à l'aide d'une caméra Pixy. Mais l'utilisation d'autres caméras est naturellement possible. Une vidéo de cette coupe de France est disponible maintenant ainsi que son Règlement 2020.


Nous allons étudier dans cette section la communication SPI avec la caméra. Il faut savoir qu'il existe une librairie C++ fonctionnant directement sur Arduino et qui s'installe directement dans l'environnement Arduino. La caméra est fournie avec un câble qu'il est possible de brancher directement sur le programmateur ICSP d'une carte Arduino. Le protocole utilisé avec ce câble est évidemment le protocole SPI mais il a une particularité, c'est qu'il n'a pas besoin d'une broche supplémentaire pour faire le "Slave Select". Il est possible d'utiliser d'autres protocoles série et en particulier le SPI avec Slave Select mais aussi la communication série RS232 ou l'i2c.

En toute franchise, nous en sommes arrivés à court-circuiter la librairie Arduino parce que nous avons commencé à résoudre ce problème dans un FPGA et que le processeur que l'on utilise dans le FPGA n'est pas complètement compatible avec un Arduino. Nous avons donc voulu le réaliser avec une carte Arduino parce que des essais directs à partir du FPGA n'ont pas été concluants. Pour tout vous dire, les essais sur Arduino nous ont pris un certain temps aussi... et c'est pour cela que nous publions ici nos résultats.

Pourquoi avons-nous eu autant de difficultés ?

  • première raison, nous ne possédons pas d'outils pour déboguer le SPI (oscilloscope ou autre)
  • deuxième raison, après avoir résolu partiellement la première raison en affichant des résultats par la liaison série, nous ne nous sommes pas aperçu tout de suite que les deux octets d'entête de la réponse arrivaient mais avec du retard. Une fois que nous avons remarqué ce détail, nous avons écrit un sous-programme qui laisse tomber ce qu'il reçoit par le SPI tant qu'il n'a pas reçu ces deux octets.
  • troisième raison, nous n'avons pas détecté dans le code source de la librairie Arduino ce type d'attente/synchronisation.

Il est maintenant grand temps de passer à la pratique.

Protocole d'échange

modifier

La connaissance du protocole d'échange entre la caméra et votre processeur est importante. Ce protocole ne dépend pas de la façon matérielle choisie : i2c, SPI sans SS, SPI avec SS, rs232. Voici comment il est documenté dans le document officiel (très partiellement traduit ici). Nous allons examiner par exemple le cas de la recherche de la résolution de la caméra.

  • La demande réalisée par votre processeur doit être conforme à la requête standard pour ce type de données :
Requête
Byte Description Value(s)
0 - 1 16-bit sync 174, 193 (0xc1ae)
2 Type de paquet 12
3 Longueur de données 1
4 Type (non utilisé réservé pour les versions futures) 0 - 255

Cette suite d'octets est obligatoirement à envoyer si vous voulez obtenir la réponse correspondant à la résolution de l'image en pixels de la part de la caméra Pixy2. Cette résolution pouvant être réglée à l'aide de PixyMon, il vous faut être sûr de la connaître si vous voulez positionner correctement un objet (par rapport à vous) dont les coordonnées vous sont retournées par la caméra.

  • La réponse de la caméra doit être conforme à :
Réponse
Byte Description Value(s)
0 - 1 16-bit sync 175, 193 (0xc1af)
2 Type de paquet 13
3 Longueur des données 2
4 - 5 16-bit checksum sum of payload bytes
6 - 7 16-bit Largeur d'image (en pixels) 0 - 511
8 - 9 16-bit Hauteur d'image(en pixels) 0 - 511

Nous allons maintenant nous intéresser à la façon de gérer ce type de données en langage C. Deux façons nous viennent à l'esprit :

  • Utilisation d'un type structure en C
/**** déclaration du type avec une structure ****/
/**** correspond au premier tableau ci-dessus ****/
struct getRes {
  uint16_t _16bitsSync;
  uint8_t TofPacket;
  uint8_t  LenOfPayload;
  uint8_t unUsed;
}; 
/**** déclaration avec initialisation d'une variable ****/
struct getRes requestRes = {0xC1AE,12,1,0};
  • Utilisation d'un tableau
/**** déclaration avec initialisation du tableau ****/
uint8_t ResData[5]={0xAE,0xC1,12,1,0};

Les deux méthodes ont leurs avantages et inconvénients. L'intérêt d'une structure est qu'elle permet d'accéder aux champs directement par leurs noms. Son inconvénient est qu'en général les sous-programmes utilisés pour envoyer ou recevoir des données utilisent des tableaux. Il faudra donc transtyper et ceci vous vaudra un warning de la part du compilateur.

L'inconvénient du tableau est qu'il ne faut pas vous tromper pour mettre les données : regardez comment est mis la valeur 0xC1AE dans le tableau. Vous aurez évidemment le même problème pour retrouver les données.

Nous vous laissons choisir.

Exercice 3

modifier

Nous nous proposons d'écrire un programme C capable de gérer le SPI sans "slave select" (puis avec) pour récupérer les données de position de de largeur et grosseur de l'objet détecté. Les données seront envoyées par la liaison série pour les tests.

  1. Chercher dans cette section et dans la précédente du code pour utiliser la liaison série et la liaison SPI. Éventuellement vous pouvez simplifier le code sachant que la liaison série ne sera utilisée que dans un sens.
  2. C'est votre processeur qui sera le maître de la liaison SPI. La caméra Pixy2 sera donc votre esclave.
    1. Dans un premier temps on utilisera la liaison SPI sans "Slave Select". Votre maître SPI est sensé avoir une horloge à 2 MHz (horloge processeur / 8) au maximum et être en mode 3 (bits CPOL=1 et CPHA=1 du registre SPCR. Modifier votre programme d'initialisation trouvé plus haut pour répondre à ces contraintes.
    2. Écrire maintenant des sous-programmes génériques pour écrire un ou plusieurs octets ainsi pour demander une lecture d'octets réponses de la Pixy2.
    3. Examinez dans la documentation appropriée le protocole d'échange pour getResolution() et pour getBlocks(sigmap, maxBlocks) et construisez des types de données pour réaliser correctement ces protocoles ainsi que les sous-programmes correspondants. Testez.
    4. Arrivés ici, vous remarquez que la communication ne se fait pas correctement. Pour un fonctionnement correct, on vous demande d'écrire un sous-programme qui commence à lire les données de la Pixy que quand il vient de recevoir l'entête de la réponse 0xC1AF sachant que la réception se fait d'abord par 0xAF puis par 0xC1. Il serait très bien de mettre un compteur qui limite à 50 les données lues en attente de 0xC1AF. En cas de limite dépassée, on sort du sous-programme avec la valeur 0 pour dire que l'on a rien lu. Dans le cas contraire, c'est le nombre d'octets lus qui est retourné.
  3. Modifier le programme précédent pour utiliser le Slave Select. On rappelle que n'importe quelle broche de l'Arduino peut servir à cela. L'utilisation de la broche notée SS ("Slave Select") dans la documentation n'est obligatoire que si votre processeur est un esclave SPI.

Voir aussi

modifier

Communication I2C

modifier

Le protocole I²C est un protocole d'échange pour les composants électroniques différents d'un même appareil. Son intérêt par rapport aux protocoles examinés auparavant est qu’il y a une notion d'adressage des esclaves. En clair un maître peut adresser plusieurs esclaves. Quand l'esclave se reconnaît par son adresse, il répond au maître.

Voici quelques brochages utiles qu’il est facile de retrouver sur Internet.

I2C SCL SDA
UNO PC5 (Arduino:A5) PC4 (Arduino:A4)
LEONARDO PD0 (Arduino:SCL) PD1 (Arduino:SDA)
Pro Micro (Sparkfun) PD0 (Arduino:3) PD1 (Arduino:2)

Adaptation de tensions

modifier

Les cartes Arduino que nous utilisons sont en général en 5V. Il existe cependant un certain nombre de périphériques I2C qui sont en 3,3 V. La manette Nunchuk en est un exemple. Il faut donc réaliser une adaptation bidirectionnelle. Ce genre d'adaptation est décrit par exemple dans ce lien. La manette Nunchuk est quant à elle décrite ICI.

Interface I2C dans l'AVR

modifier

Programmation de l'interface I2C décrit en anglais ce que nous allons présenter dans ce chapitre.

 
Registres de ATMega328 pour i2c master

Le premier registre important est TWBR qui est utilisé pour régler la fréquence d'horloge de la broche SCL. Il faut ajouter au contenu de ce registre les deux bits TWPS1 et TWPS2 du registre TWSR qui sont destiné à réaliser un préscaler avec les valeurs 1, 4, 16 et 64.

Les documentations officielles donnent la formule de calcul de la fréquence :

 

Comme toujours, il existe un registre de contrôle TWCR qui a un ensemble de bits utilisés pour :

  • autoriser l'interruption TWIE
  • autoriser le module i2C TWEN : le module prend alors le contrôle physique sur les broches dédiées à l'i2c
  • envoi d'un départ (start) TWSTA : doit être remis à 0 par le logiciel quand le start est effectif
  • envoi d'un stop TWSTO
  • Drapeau d'interruption TWINT qui fonctionne comme un drapeau : mis à 0 par écriture d'un 1. Il peut être utilisé pour détecter une fin de transmission. Il faut un "sei()" et TWIE = 1 en supplément pour déclencher une interruption. Cette possibilité de l'utiliser sans interruption fait qu'il n'est pas mis automatiquement à zéro par l'exécution de l'interruption comme certains autres drapeaux.
  • autorise l'envoi d'un ack avec TWEA quand une donnée de l'esclave est reçue

Le registre d'état déjà mentionné TWSR contient les bits de réglage du présscaler. Mais son utilisation principale est de donner l'état du bus I2C avec les bits TWS[7:3]. TWDR est le registre de données qui est utilisé pour mémoriser le prochain octet à transmettre ou l'octet reçu. Les registres TWAR et TWARM sont utilisés quand l'AVR travaille en mode esclave.


Une bibliothèque simple pour utiliser l'i2c

modifier

Programmation de l'interface I2C donne un ensemble de sous-programmes suffisamment intéressants pour être repris ici.

Commençons à une initialisation à 400 kHz :

void TWIInit(void)
{
    //set SCL to 400kHz
    TWSR = 0x00;
    TWBR = 0x0C;
    //enable TWI
    TWCR = (1<<TWEN);
}

Nous n'avons pas besoin de préscaler. Pour ce qui est du départ et de l'arrêt, ces deux sous-programmes sont proposés :

//send start signal
void TWIStart(void)
{
    TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
    while ((TWCR & (1<<TWINT)) == 0);
    //TWCR &= ~(1<<TWSTA); //RAZ logiciel du bit TWSTA en commentaire avant des tests
}

//send stop signal
void TWIStop(void)
{
    TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);
}

Pour "start" nous devons positionner TWSTA et pour "stop" TWSTO ainsi que les bits TWINT et TWEN. Après qu'un "start" est demandé, il nous faut attendre jusqu'à ce que le bit TWINT passe à un.

Un sous-programme d'écriture peut être :

void TWIWrite(uint8_t u8data)
{
    TWDR = u8data;
    TWCR = (1<<TWINT)|(1<<TWEN);
    while ((TWCR & (1<<TWINT)) == 0);
}

La lecture est plus compliquée car elle nécessite deux formes :

  • une avec acquittement
  • une sans acquittement

Voici les deux sous-programmes correspondants :

//read byte with ACK
uint8_t TWIReadACK(void)
{
    TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWEA);
    while ((TWCR & (1<<TWINT)) == 0);
    return TWDR;
}

//read byte with NACK
uint8_t TWIReadNACK(void)
{
    TWCR = (1<<TWINT)|(1<<TWEN);
    while ((TWCR & (1<<TWINT)) == 0);
    return TWDR;
}

Pour compléter tout ceci, une lecture de l'état est nécessaire :

uint8_t TWIGetStatus(void)
{
    uint8_t status;
    //mask status
    status = TWSR & 0xF8;
    return status;
}

Nous allons utiliser ces sous-programmes pour lire la manette Nunchuk.

Application à la manette de jeux Nunchuk en mode Arduino

modifier
 

Cette section a été réalisée avec un Arduino 3,3 V. En effet la manette Nunchuk fonctionne avec cette tension basse. La brancher directement sur un Arduino UNO peut endommager la manette !

Un chapitre de ce livre (parmi les chapitres suivants) est consacré à l'Arduino. Si vous n'avez aucune connaissance sur l'Arduino, lisez-le au moins en partie. Si vous avez de bonnes connaissances en C, il vous suffit cependant de savoir qu'un programme Arduino est composé par un setup() et un loop() en lieu et place du célèbre main(). Le setup() est réalisé une seule fois au démarrage et le loop() est réalisé en boucle (loop en anglais se traduit par boucle en français).

La manette Nunchuk est décrite dans un autre projet : Les nouvelles interfaces : de la nunchuk de Nintendo à android où seule la partie concernant la Nunchuk est à lire.

 
Documentation de la Wii-Nunchuk

Voici quelques brochages utiles qu’il est facile de retrouver sur Internet.

I2C SCL SDA
UNO PC5 (Arduino:A5) PC4 (Arduino:A4)
LEONARDO PD0 (Arduino:SCL) PD1 (Arduino:SDA)
Pro Micro (Sparkfun) PD0 (Arduino:3) PD1 (Arduino:2)

Wii Nunchuk help donne aussi des informations importantes sur la manette Nunchuk. En particulier, il vous faut garder à l'esprit que cette manette est faite pour fonctionner sous 3,3 V alors que le monde Arduino fonctionne en général plutôt à 5 V.

  Deuxième piqûre de rappel : le risque de détruire une manette Nunchuk est donc réel si vous la connectez à une platine Arduino. Faites une adaptation comme décrit plus haut en tout début de la section sur l'i2c.

Nous avons choisi la Platine "Pro Micro - 3,3 V/8MHz" de chez Sparkfun pour faire les tests de cette section. Comme elle est alimentée en 3,3 V, on n'a aucun problème de compatibilité.

Le point essentiel est que la manette Nunchuk retourne 6 octets dont la signification est la suivante :

Bit
Byte 7 6 5 4 3 2 1 0
0 SX<7:0>
1 SY<7:0>
2 AX<9:2>
3 AY<9:2>
4 AZ<9:2>
5 AZ<1:0> AY<1:0> AX<1:0> BC BZ

(tableau tiré de wiibrew)

SX,SY sont les positions du Joystick analogique en X et Y, tandis que AX, AY, et AZ sont les donnée de l'accéléromètre sur 10 bits suivant les trois axes.

Voici un exemple de lecture des accéléromètres en langage Arduino :

#include <Wire.h>;
 // adresse I2C du nunchuck
#define WII_NUNCHUK_I2C_ADDRESS 0x52
 
// définition d'une variable counter
uint8_t counter;
 
// définition d'un tableau de données
uint8_t data[6];

void setup() {
  Serial.begin(9600);
  // initialisation du nunchuck
  Wire.begin();
  Wire.beginTransmission(WII_NUNCHUK_I2C_ADDRESS);
  Wire.write(0xF0);
  Wire.write(0x55);
  Wire.endTransmission();
 
  Wire.beginTransmission(WII_NUNCHUK_I2C_ADDRESS);
  Wire.write(0xFB);
  Wire.write(0x00);
  Wire.endTransmission();
}
 
 
void loop() { 
  // on demande 6 octets au nunchuck
  Wire.requestFrom(WII_NUNCHUK_I2C_ADDRESS, 6);
  counter = 0;  // tant qu’il y a des données
  while(Wire.available()) {
    // on récupère les données
    data[counter++] = Wire.read();
  }
 
  // on réinitialise le nunchuck pour la prochaine demande
  Wire.beginTransmission(WII_NUNCHUK_I2C_ADDRESS);
  Wire.write(0x00);
  Wire.endTransmission();
 
  if(counter >= 5){
     // on extrait les données
     // dans mon exemple j'utilise uniquement les données d'accélération sur l'axe Y
     int16_t accelX = ((data[2] << 2) + ((data[5] >> 2) & 0x03));
     int16_t accelY = ((data[3] << 2) + ((data[5] >> 4) & 0x03));
     int16_t accelZ = ((data[4] << 2) + ((data[5] >> 6) & 0x03));
     Serial.print("ax = ");
     Serial.print(accelX);
     Serial.print(" : ay = ");
     Serial.print(accelY);
     Serial.print(" : az = ");
     Serial.println(accelZ);
   }
   // un petit delai  pour pas saturer la liaison série.
   delay(1000);
}

Travail à faire

modifier

Si l'accéléromètre donne une valeur quand ses positions changent c’est tout simplement parce qu’il est sensible à l'accélération de pesanteur.

1°) On vous demande les valeurs envoyées par l’accéléromètre pour chacun des axes en positif et négatif. Profitez de cet exercice pour bien repérer les trois axes x, y et z de l'accélération. Pour cela chercher à rendre maximum l'accélération sur un des axes : la verticale vous donnera la direction de l'axe correspondant.

2°) On vous demande de repérer le point 0 d'accélération (entre les deux extrêmes, puis de convertir les valeurs données par l'accéléromètre en unité standard   sachant que l'accélération de pesanteur vaut 9,81 S.I. ( )

3°) Choisissez un axe et réalisez la plus grande accélération possible. Combien de ms⁻2 faites-vous (sans vous démonter l'épaule) ?

4°) Faire un programme qui calcule sans arrêt les minima et maxima des accélérations tant que vous n'appuyez pas sur le bouton Z et affiche les résultats si vous appuyez dessus.

Application à la manette Nunchuk en C pur

modifier

Un exemple peut être trouvé ICI sur Internet.

Communication USB

modifier

Il y a deux librairies avancées pour l'USB :

  • VUSB
  • LUFA

L'utilisation de la librairie LUFA de manière transparente dans le monde Arduino est développée dans un autre chapitre avec la réalisation d'un clavier USB.

Nous allons commencer par la librairie VUSB.

Un clavier USB qui utilise la librairie VUSB

modifier

Voir aussi

modifier

Une liaison série qui utilise la librairie VUSB

modifier

Voir aussi

modifier

Utilisation de la platine Arduino Leonardo pour réaliser une liaison série USB

modifier

La platine Arduino Leonardo est équipée d'un AVR32U4 qui permet de faire de l'USB de manière matérielle. L'environnement de développement Arduino détourne automatiquement les "Serial.print()" pour utiliser la liaison série à travers l'USB. Mais qu'en est-il si vous voulez travailler en C pur ? C'est à cette question que nous allons tenter de répondre dans cette section.

Dans le chapitre sur Arduino de ce livre nous parlons de l'USB sur cette platine. Vous pouvez trouver cela dans la section Clavier USB avec la platine Leonardo. Cela nous montre la simplicité que procure l'environnement Arduino pour résoudre des problèmes assez complexes. Pourtant, si l’on veut aller plus loin il va bien falloir comprendre l’utilisation des registres pour gérer l'USB. C'est ce que nous nous proposons d'examiner maintenant.

Premier essai

modifier

Il est possible de partir des fichiers Liaison série avec Leonardo trouvés dans le Wiki de l'IUT de Troyes.

Le fichier d'exemple du lien précédent comporte deux main() mais si vous le regardez attentivement le premier est entouré par un #if 0 suivi du code et suivi d'un else. Par défaut ce ne sera pas ce main qui sera compilé.

#if 0
// Very simple character echo test
int main(void)
{
	CPU_PRESCALE(0);
	usb_init();
	while (1) {
		int n = usb_serial_getchar();
		if (n >= 0) usb_serial_putchar(n);
	}
}

#else

L'autre main() réalise un mini terminal série avec comme écran d'accueil :

Teensy USB Serial Example, Simple Pin Control Shell

Example Commands
  B0?   Read Port B, pin 0
  C2=0  Write Port C, pin 1 LOW
  D6=1  Write Port D, pin 6 HIGH  (D6 is LED pin)

Sur la carte Arduino Leonardo la célèbre LED 13 est câblée sur le bit 7 du PORTC. Ainsi l'envoi de la commande C7=0 vous éteindra cette LED 13 tandis que C7=1 l'allumera....

Détails de la compilation du premier essai

modifier

Le problème est que ces fichiers sources ne se compilent pas facilement avec l'environnement Arduino.

  • En effet celui-ci essaie de compiler avec avr-g++ mais ce dernier refuse de compiler des structures contenant un tableau non dimensionné. Il faut le forcer à utiliser avr-gcc pour résoudre le problème. Un autre essai nous a montré qu'il utilisait en fait les deux compilateurs. Mais l'erreur de double définition des vecteurs d'interruptions 10 et 11 (spécifiques à l'USB) montre que dans ce cas il initialise déjà la liaison USB. Étrange, nous avions toujours pensé que la présence d'un "main()" dans un programme Arduino mettait de côté toutes les initialisations nécessaires au bon fonctionnement de l'environnement Arduino !
  • L'utilisation de avr-gcc peut être directement réalisée avec un autre environnement que l'environnement Arduino comme par exemple Eclipse avec son plugin avr. Avec cet environnement tout ceci se compile directement.
  • L'utilisation d'avr-gcc peut s'utiliser manuellement. Si vous avez l'environnement Arduino installé vous avez automatiquement la chaîne de compilation des AVR installée et vous disposez alors de deux possibilités pour compiler :
    • réaliser un Makefile
    • réaliser un script
Avec makefile
modifier

Voici le makefile que l'on utilise pour compiler :

# All Target
all:example.o usb_serial.o example.elf example.hex

example.o:
	avr-gcc -g -mmcu=atmega32u4 -Wall -Os -c example.c

usb_serial.o:
	avr-gcc -g -mmcu=atmega32u4 -Wall -Os -c usb_serial.c

example.elf:
	avr-gcc -g -mmcu=atmega32u4 -o example.elf -Wl,-Map,example.map example.o usb_serial.o

example.hex:
	avr-objcopy -R .eeprom -R .fuse -R .lock -R .signature -O ihex example.elf  "example.hex"

clean:
	rm *.o
	rm *.elf
	rm *.hex
	rm *.map

Et maintenant voici le script que l'on utilise pour utiliser avrdude :

#!/bin/bash

/home/serge/arduino-1.8.9/hardware/tools/avr/bin/avrdude -C/home/serge/arduino-1.8.9/hardware/tools/avr/etc/avrdude.conf -v -patmega32u4 -cavr109 -P/dev/ttyACM0 -b57600 -D -Uflash:w:example.hex:i