Cours:TPS 2103 5

De troyesGEII
Aller à : navigation, rechercher

Retour à la liste des Tps

Éléments de correction

Communication série asynchrone rs232

Vous avez déjà eu l'occasion d'utiliser cette liaison avec les primitives Arduino. Rappelez-vous le programme utilisé pour sortir les valeurs des capteurs analogiques :

void setup()
{
  Serial.begin(9600);
}
 
void loop()
{
  Serial.print("Photocoupleur : ");
  Serial.println(analogRead(A2),DEC);
  delay(500);  
}

Nous allons maintenant utiliser cette liaison en C.

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)

Présentation pour la programmation en C

Une librairie d'utilisation

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
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 MEGA2560 ou une carte UNO mais ne peut pas être utilisée avec une carte Leonardo !!!

Cas particulier du Leonardo

Le ATMega32U4 qui équipe la carte Leonardo ne possède qu'une seule USART (l'autre passe par l'USB et est donc gérée de façon complètement différente)... et du coup les numéros de registres disparaissent... ainsi UBRR0H est remplacée par UBRRH.

L'autre problème est que le Rx et Tx sont des signaux 5V qui ne peuvent donc en aucun cas être reliés directement à un PC ! Si l'on veut le réaliser, le mieux est actuellement d'utiliser un circuit pour transformer tout cela en USB. On peut utiliser un circuit Maxim ou un ATMega8U1...

Travail à réaliser en C

Dans cette partie on réalisera le changement de vitesse de transmission puis au choix :

  • Changer la fréquence d'un buzzer
  • Changer le rapport cyclique

Il y a bien sûr beaucoup de travail identique dans les deux cas.

Changer la vitesse de transmission

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.

Question.jpg 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

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.

Question.jpg É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.

Question.jpg 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.

Et l’apothéose finale ....

Question.jpg 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.

Changer un rapport cyclique

On désire changer l'intensité d'éclairage d'une LED à l'aide d'une PWM rapide sur le timer 0. La valeur du rapport cyclique varie entre 0 et 255 et 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.

Question.jpg É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.

Question.jpg Un sous-programme "unsigned char usart_gets_hexa()" sera donc chargé de lire ces deux caractères d'en vérifier la syntaxe. Écrire ce sous-programme et le tester avec les afficheurs de notre shield. On affichera "--" en cas d'erreur de syntaxe.

Et l’apothéose finale ....

Question.jpg Changer votre programme en utilisant les sous-programmes développés pour la question précédente pour qu'il réalise un rapport cyclique 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.

Travail à réaliser en C Arduino

Reprendre l'exercice précédent en cherchant à simplifier au mieux ce qui a été réalisé dans la section précédente. Cherchez par exemple :

  • les fonctions toutes faites du monde Arduino pour afficher une valeur hexadécimale : Print fait-il l'affaire ?
  • Parsint est-il utilisable ? Que doit-on changer dans l'exercice pour l'utiliser ?
  • le rapport entre un rapport cyclique variable et analogWrite

Communication par bus SPI

SPI est un article qui présente le protocole SPI dans ce WIKI. Il a en effet été utilisé par des étudiants pour communiquer entre un FPGA et un Arduino.

Vous pouvez aussi lire Serial Peripheral Interface dans Wikipédia.

Brochages de quelques cartes Arduino pour le SPI

Voici présenté les brochages utilisés par les cartes Arduino que nous possédeons à l'IUT :

SPI MISO MOSI SCK SS
UNO PB4 (Arduino:12) PB3 (Arduino:11) PB5 (Arduino:13) PB2 (Arduino:10)
LEONARDO PB3 (ICSP:1) PB2 (ICSP:4) PB1 (ICSP:3) -
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) -

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. Il n'y a aucune broche dédiée au SS. Cela signifie-t-il que le Leonardo ne peut pas fonctionner en esclave SPI ?

Pour éviter des recherches sur internet, nous rappelons la connectique associée des six broches avec les deux photos ci-dessous :

Connectique SPI

Voici par exemple ci- dessous la connectique ICSP présente sur une carte Arduino.

Connectique ISP de l'Arduino

Travail en C

Introduction

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 
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(10);
  }
}

Examinez le programme et répondez à la question :

Question.jpg En utilisant les brochages donnés plus haut, pouvez-vous dire si ce programme fonctionne tel quel sur une carte Arduino UNO ? Que faut-il modifier pour le faire fonctionner sur une carte Leonardo ?

Application

Vous disposez maintenant d'un esclave SPI : carte UNO sans shield. A l'aide d'une plaque à essai vous allez brancher une LED avec une résistance pour un bon fonctionnement du programme ci-dessous (qui utilise le timer 0). Vous allez utiliser le programme suivant dans cette carte (esclave):

//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
  }
}

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 la LED.

Question.jpg Réaliser les branchements entre les deux cartes Arduino pour un fonctionnement correct. Le SPI maître est susceptible d'envoyer le rapport cyclique par liaison SPI au deuxième Arduino. Pour le maître, vous pouvez utiliser la liaison série pour lui envoyer le rapport cyclique à positionner dans l'esclave.

Travail à réaliser en C Arduino

Refaire le travail de l'exercice précédent (pour le SPI maître uniquement) complètement en C Arduino. On vous donne un programme d'exemple qu'il faudra bien sûr modifier.

#include <SPI.h>
void setup (void) {
  pinMode(8,OUTPUT);
  pinMode(7,INPUT);
  digitalWrite(SS, HIGH);
// ensure SS stays high for now
// Put SCK, MOSI, SS pins into output mode
// also put SCK, MOSI into LOW state, and SS into HIGH state.
// Then put SPI hardware into Master mode and turn SPI on
  SPI.begin ();
  Serial.begin(9600);
// Slow down the master a bit
   SPI.setClockDivider(SPI_CLOCK_DIV128);
//little indian or big indian ?
//   SPI.setBitOrder(MSBFIRST);
//   SPI.setBitOrder(LSBFIRST);
} // end of setup

void loop (void) {
//déclaration variable
  unsigned char dataByte;
// enable Slave Select
  digitalWrite(SS, LOW);
// SS is pin 10
  dataByte=SPI.transfer (85);
  Serial.println(dataByte,DEC);
  digitalWrite(SS, HIGH);
  delay (200);
// 1 seconds delay
} // end of loop

Dialoguer en i2c avec une manette 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)

Nous allons maintenant évoquer le problème des adaptations de tensions.

Adaptation de tensions

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,3V. 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 dans la WIKIVersité et aussi ici (rédigé par les étudiantes KONAK Sumeyye et SAVRY Maelle en 2013/2014).

Exemple de programme

La manette Nunchuk n'est pas faite pour être utilisée avec d'autres périphériques I2C (à part le maître). Par conséquent elle a une adresse fixe : 0x52 qu'il est facile de repérer dans le programme ci-dessous.

#include <Servo.h>;
#include <Wire.h>;

// Doit être ajusté en fonction de chaque nunchuck
#define ZEROX 530
#define ZEROY 530
#define ZEROZ 530


// adresse I2C du nunchuck
#define WII_NUNCHUK_I2C_ADDRESS 0x52

// définition d'une variable Servo
Servo servomoteur;
// définition d'une variable counter
int counter;

// définition d'un tableau de données
uint8_t data[6];


void setup()
{
  // on attache le servomoteur à la pin 11 (PWM) 
  servomoteur.attach(11);
  // 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
     int accelX = ((data[2] << 2) + ((data[5] >> 2) & 0x03) - ZEROX);
     int accelY = ((data[3] << 2) + ((data[5] >> 4) & 0x03) - ZEROY);
     int accelZ = ((data[4] << 2) + ((data[5] >> 6) & 0x03) - ZEROZ);

     // on limite la valeur entre -180 et 180
     int value = constrain(accelY, -180, 180);
     // on mappe cette valeur pour le servomoteur soit entre 0 et 180
     value = map(value, -180, 180, 0, 180);
     // on écrit sur le servomoteur la valeur
     servomoteur.write(value);

     // un petit delai pour pas saturer le servomoteur
     delay(100);
   }
}

Travail à réaliser en C Arduino

Lisez le programme ci-dessus et répondez aux questions suivantes :

Question 1

Question.jpg Réaliser un programme capable de sortir correctement les informations des positions et des accéléromètres sur la liaison série. Par correctement, nous voulons dire en format décimal (mais non signé), c'est à dire dans un format lisible par tous. Et donc pas trop vite si l'on veut lire : il faudra donc le faire toutes deux les secondes par exemple.

Ce que l'on vous demande de faire consiste à supprimer tout ce qui concerne le servo et à le remplacer par de l'affichage sur la liaison série.

Profitez de ce changement pour retirer les offsets ZEROX, ZEROY et ZEROZ. On utilisera notre propre méthode de calibration plus tard.

Vous pouvez par la même occasion éliminer la contrainte [fonction constrain()].

Question 2

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.

Question.jpg On vous demande les valeurs envoyées par l’accéléromètre pour chacun des axes en positif et négatif. Vous en profiterez pour essayer de trouver l'orientation des trois axes d'accélération. Notez toutes ces valeurs quelquepart, vous en aurez besoin dans la suite.

Ce travail nécessite de la méthode, ne serait-ce que pour retrouver les axes. Réfléchissez par vous même.

Question 3

Question.jpg 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 ms⁻2 sachant que l'accélération de pesanteur vaut 9,81 S.I.

Il est difficile d'éviter les erreurs de calibrations : l'accélération de pesanteur dépassera par exemple 9,81 SI au repos. Cela est du essentiellement à la manière simpliste avec laquelle nous vous proposons de calibrer : repère max et min et on hasarde l'hypothèse que le zéro est au milieu !

Nous n’admettrons pas de valeurs dépassant 10,2 ou en-dessous de 9,4 pour g !

Question 4

En mettant en pratique le programme de la question 3 :

Question.jpg Choisissez un axe et réalisez la plus grande accélération possible. Combien de ms⁻2 faites-vous ?

Il n'y a aucun programme à faire pour cette question, juste à vous démonter un peu l'épaule.

Combien de g sont-il mesurables avec cet accéléromètre ?

Avant de passer à la suite, estimez les accélérations réalisées par un tremblement de type Parkinson. Pour cela rechargez le programme pour partir de valeurs toutes neuves. Peut-on utiliser une Nunchuk pour détecter cette maladie ?

Question 5

Attention, pour les boutons, la valeur retournée est 1 quand on n'appuie pas dessus et 0 dans le cas contraire.

Question.jpg 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.

Travail en C

I2C/TWI wii nunchuck help vous donne un programme en C pur utilisant la manette Nunchuk et affichant sur un écran Texte les valeurs lues.

Question.jpg Réaliser un programme capable de sortir correctement les informations des positions et des accéléromètres sur la liaison série. Le projet de l'exemple est sur plusieurs fichiers : vous allez donc apprendre à gérer ce genre de situation dans l'environnement Arduino.