AVR Studio est un environnement de développement gratuit réalisé par Atmel.
AVR Studio 6 intègre par défaut un compilateur C.
Démarrez AVR Studio
Cliquez sur “New project”
Sélectionnez “GCC C Executable project”
Indiquez un nom de projet (mon_projet) et un emplacement Exemple : C:\Projet_AVR\mon_projet\:
Sélectionner votre microcontrôleur
Cliquez sur “OK”
Entrez le code suivant dans le fichier mon_projet.c :
/* * Test.c * Mise à la masse du bit 0 du port R pour l'allumage d'une LED * Created: 20/08/2014 00:24:08 * Author: -JP- */ #include <avr/io.h> int main(void) { PORTR.DIR |=0b00000001; //Met le bit 0 port R en sortie PORTR.OUT &=~0b00000001; //Met le bit 0 port R à la masse while(1) { //TODO:: Please write your application code } }
Sauvegardez le projet : Fichier → Enregistrer Tout
Compilez le projet : Générer → Générer la solution
Vous ne devriez pas avoir d'erreur :
Dans Project → Propriétés de votre projet, sélectionner “Simulator” dans Tools → Selected Debugger/programmer
Positionner un point d’arrêt à la ligne “PORTR.OUT &=~0b00000001;” en cliquant à gauche de la ligne.
Aller dans le menu Déboguer → Continuer.
Votre programme va alors s’exécuter jusqu'au point d’arrêt.
Cliquer sur le bouton “I/O view”, sélectionner le PORTR. Vous voyez dans le registre “DIR” que le premier bit a bien été mis à 1 pour indiquer que la pin doit être utilisée en sortie.
ASF est un framwork developpé pour faciliter le développement de vos applications.
Pour commencer, nous allons créer un projet du type “GCC ASF Board Project”. Spécifier le microcontroleur que vous utilisez puis “User Board template”. Dans la fenetre ASF Wizard“ selectionner votre projet.
Aller dans ASF → ASF Wizard
Dans project, sélectionner votre projet
Selectionner “GPIO - General purpose Input/Output (service)
Cliquer sur “Add”cliquer sur Add pour l'intégrer à votre projet.
Cliquer sur “Apply” pour confirmer l'importation.
Vous pouvez maintenant utiliser les fonctions de cette librairie. Pour accéder à la descriptions détaillées de ces fonctionner aller dans ASF Explorer → GPIO → API Documentation
La librairie GPIO contient les fonctions de base permettant de lire, configurer ou commander une pin d'entrée/sortie.
La librairie IOPORT est également incluse dans le “pack” GPIO et vous offre quelques fonctionnalités supplémentaires en se basant sur la librairie GPIO.
Nous allons ajouter dans notre projet
#include <ioport.h>
pour pouvoir utiliser certains mot-clés.
La première chose à faire et de définir quelle pin vous souhaitez commander. Pour ma part, je souhaite piloter une led qui est connectée à PD4. Pour simplifier la lisibilité du code, nous allons définir un nom pour cette sortie :
#define LED_ROUGE IOPORT_CREATE_PIN(PORTD, 4)
Ensuite, il faut initialiser les fonctions IOPORT avant de pouvoir les utiliser.
ioport_init();
Vous devez également définir la direction de votre pin
ioport_set_pin_dir(LED_ROUGE, IOPORT_DIR_OUTPUT);
Ma led est allumée quand PD4 est à 0V. Les XMEGA permettent d'inverser facilement la commande d'une sortie et ensuite avoir une code plus “logique” avec un allumage de la led quand la sortie est mise à “1”.
ioport_set_pin_mode(LED_ROUGE,IOPORT_MODE_INVERT_PIN);
Pour finir par la mettre à l'état haut
ioport_set_pin_level(LED_ROUGE, 1);
Ce qui vous fait, au final
#include <avr/io.h> #include <ioport.h> #define LED_ROUGE IOPORT_CREATE_PIN(PORTD, 4) //LED_Rouge = PD4 int main(void) { //Initialisation des entrées/sorties ioport_init(); //Initialisation d'IOPORT ioport_set_pin_dir(LED_ROUGE, IOPORT_DIR_OUTPUT); //LED_ROUGE est une sortie ioport_set_pin_mode(LED_ROUGE,IOPORT_MODE_INVERT_PIN); //Inverse la commande de la pin LED_ROUGE ioport_set_pin_level(LED_ROUGE,1); //LED_Rouge à l'état haut while(1) { //TODO:: Please write your application code } }
On va ajouter à notre code la lecture d'un bouton et modifier l'état de la led en fonction de l'appui du bouton. De la même maniére que pour la LED, nous allons nommer cette entrée bouton.
#define BOUTON IOPORT_CREATE_PIN(PORTF, 1) //BOUTON = PF1
Mon bouton met une masse sur l'entrée lors de l'appui. Tout comme pour la LED, il est plus logique d'inverser cette entrée afin d'avoir un état “1” lors de l'appui.
ioport_set_pin_dir(BOUTON, IOPORT_DIR_INPUT); //BOUTON en entrée ioport_set_pin_mode(BOUTON,IOPORT_MODE_INVERT_PIN); //Inverse l'état de la pin
Nous allons ensuite tester de manière permanente l'état du bouton. Si le bouton est appuyé, la LED sera allumée.
if(ioport_get_pin_level(BOUTON)) //Appui sur le bouton ioport_set_pin_level(LED_ROUGE,1); //Allume la LED else //Relachement du bouton ioport_set_pin_level(LED_ROUGE,0); //Eteind la LED
Voici le code complet :
#include <avr/io.h> #include <ioport.h> #define LED_ROUGE IOPORT_CREATE_PIN(PORTD, 4) //LED_Rouge = PD4 #define BOUTON IOPORT_CREATE_PIN(PORTF, 1) //BOUTON = PF1 int main(void) { //Initialisation des entrées/sorties ioport_init(); //Initialisation d'IOPORT ioport_set_pin_dir(LED_ROUGE, IOPORT_DIR_OUTPUT); //LED_ROUGE est une sortie ioport_set_pin_mode(LED_ROUGE,IOPORT_MODE_INVERT_PIN); //Inverse la commande de la pin ioport_set_pin_dir(BOUTON, IOPORT_DIR_INPUT); //BOUTON en entrée ioport_set_pin_mode(BOUTON,IOPORT_MODE_INVERT_PIN); //Inverse l'état de la pin while(1) { if(ioport_get_pin_level(BOUTON)) //Appui sur le bouton ioport_set_pin_level(LED_ROUGE,1); //Allume la LED else //Relachement du bouton ioport_set_pin_level(LED_ROUGE,0); //Eteind la LED } }
Lors de la création d'un projet ASF avec comme template “Userboard” des fichiers de configuration par défaut sont créés.
Ces fichiers se trouvent dans le répertoire “config”. L'un d'eux, conf_clock.h, premet la configuration de l'horloge lors de son démarrage.
Par défaut, l'horloge utilisée est l'oscillateur interne de 2Mhz:
#define CONFIG_SYSCLK_SOURCE SYSCLK_SRC_RC2MHZ
CONFIG_SYSCLK_SOURCE définit la source de votre horloge système qui peut être :
SYSCLK_SRC_RC2MHZ | Oscillateur 2Mhz interne |
SYSCLK_SRC_RC32MHZ | Oscillateur 32Mhz interne |
SYSCLK_SRC_XOSC | Oscillateur externe |
SYSCLK_SRC_XOSC | PLL |
Dans le cas de l'utilisation d'un oscillateur externe, il faudra également définir son type et sa fréquence dans le fichier user_board.h
Sans prédivision de cette horloge :
#define CONFIG_SYSCLK_PSADIV SYSCLK_PSADIV_1 #define CONFIG_SYSCLK_PSBCDIV SYSCLK_PSBCDIV_1_1
Le microcontrôleur fonctionne donc à 2Mhz.
Dans cette exemple, nous allons simplement faire clignoter une LED. Pour cela, nous allons avoir besoin de créer une temporisation avec la fonction “delay” de l'ASF.
Importer les librairies qui sont nécessaires. Cliquer sur ASF → ASF Wizard. Chercher les librairies suivants :
Cliquer sur Add pour importer les librairies dans votre projet.
La fonction delay est directement liée à la fréquence de fonctionnement de votre micro.
Il est donc important de la définir dans votre projet.
Ouvrez ensuite le fichier “conf_clock.h” dans le répertoire config de votre projet.
Sélectionner le type d'horloge que vous utilisez.
Ouvrez le fichier main.c du projet et complétez avec :
#define LED IOPORT_CREATE_PIN(PORTR, 0) //LED = PR0 int main (void) { sysclk_init(); //Initialisation horloge système board_init(); //Initialisation de la carte //Initialisation des entrées/sorties ioport_init(); //Initialisation d'IOPORT ioport_set_pin_dir(LED, IOPORT_DIR_OUTPUT); //LED_ROUGE est une sortie while(1) { ioport_toggle_pin_level(LED); //Inverse l'état de la LED1 delay_ms(250); //Tempo de 250ms } }
Créer un nouveau projet ASF.
Importer les librairies qui sont nécessaires. Cliquer sur ASF → ASF Wizard. Chercher les librairies suivantes :
Nous allons utiliser l'horloge par défaut de 2Mhz.
L'objectif de cette exemple sera de faire clignoter une LED toutes les secondes.
Dans le principe de fonctionnement, le timer est cadencé par une horloge. A chaque coup d'horloge, le compteur va s'incrémenter.
Nous utiliserons le Timer 0 qui est un timer de 16bits. Il a donc, au maximum, 65535 pas d'incrémentation.
Nous voulons que la LED s'allume toutes les secondes. Le timer doit donc déclencher un interruption toutes les 500ms.
Avec un horloge à 2Mhz cela nous fait un pas d'incrémentation toutes 0.5µs même si le compteur allait jusqu'à 65535 cela nous ferait 33ms de temps entre chaque interruption sachant que nous voulons environ 500ms.
Nous avons le choix entre les diviseurs suivants :
TC_CLKSEL_DIV1_gc = (0x01<<0), /* System Clock */ TC_CLKSEL_DIV2_gc = (0x02<<0), /* System Clock / 2 */ TC_CLKSEL_DIV4_gc = (0x03<<0), /* System Clock / 4 */ TC_CLKSEL_DIV8_gc = (0x04<<0), /* System Clock / 8 */ TC_CLKSEL_DIV64_gc = (0x05<<0), /* System Clock / 64 */ TC_CLKSEL_DIV256_gc = (0x06<<0), /* System Clock / 256 */ TC_CLKSEL_DIV1024_gc = (0x07<<0), /* System Clock / 1024 */
Nous allons donc diviser la fréquence par 64. Ce qui nous fera un pas d'incrémentation toutes les 32µs. Pour avoir un période de 500ms, il faudra que notre compteur compte jusqu'à 500 000 / 32 = 15625
#include <asf.h> //Led #define LED IOPORT_CREATE_PIN(PORTR, 1) //Déclenchement toutes les 500ms static void timer0_tick(void) { ioport_toggle_pin_level(LED); //Inverse l'état de la LED } int main (void) { //Initialisation du controleur d'interruptions pmic_init(); //Initialisation de l'horloge sysclk_init(); //Initialisation des entrées/sorties ioport_set_pin_dir(LED, IOPORT_DIR_OUTPUT); //Initialisation du Timer 0 tc_enable(&TCC0); tc_set_overflow_interrupt_callback(&TCC0, timer0_tick); //Création d'un callback qui sera executé quand un overflow du timer sera déclenché. tc_set_wgm(&TCC0, TC_WG_NORMAL); //Choix du mode du timer0, dans ce cas il comptera jusqu'à sa valeur "TOP" et retombera à 0 tc_write_period(&TCC0, 15625); //Définition de la valeur "TOP" tc_set_overflow_interrupt_level(&TCC0, TC_INT_LVL_LO); //Activation de l'interruption du timer cpu_irq_enable(); //Activation de l'interruption globale tc_write_clock_source(&TCC0, TC_CLKSEL_DIV64_gc); //Activation de l'horloge du timer 0 board_init(); while(1) { } }
Explications :
pmic_init();
Initialise le controleur d'interruption. Sans ca, les interruptions du timer ne se déclencheront pas.
sysclk_init();
Initialise l'horloge système. Cf conf_clock.h (Horloge de 2Mhz sans prédivison)
tc_enable(&TCC0);
Active le timer0
tc_set_overflow_interrupt_callback(&TCC0, timer0_tick);
Définit que la fonction timer0_tick sera appelée à chaque interruption du timer 0. La fonction :
static void timer0_tick(void) { ioport_toggle_pin_level(LED); //Inverse l'état de la LED }
sera executée toutes les 500ms
tc_set_wgm(&TCC0, TC_WG_NORMAL);
Définit le mode de fonctionnement “Normal” du timer. Dans ce cas il comptera jusqu'à sa valeur “TOP”, déclenchera l'interruption overflow, repassera à 0 et ainsi de suite …
cpu_irq_enable();
Activation globale des interruptions
tc_write_clock_source(&TCC0, TC_CLKSEL_DIV64_gc);
On alimente le timer avec l'horloge système / 64
Nous avons vu dans l'exemple précédente qu'il est possible de déclencher une interruption quand un timer arrive à une certaine valeur. Il est également possible de déclencher 2 interruptions sur 2 valeurs différentes :
#include <asf.h> //Led #define LED IOPORT_CREATE_PIN(PORTR, 1) static void timer0_tickA(void) { ioport_set_pin_level(LED,1); //Allumage de la LED } static void timer0_tickB(void) { ioport_set_pin_level(LED,0); //Extinction de la LED } int main (void) { //Initialisation du controleur d'interruptions pmic_init(); //Initialisation de l'horloge sysclk_init(); //Initialisation des entrées/sorties ioport_set_pin_dir(LED, IOPORT_DIR_OUTPUT); ioport_set_pin_mode(LED,IOPORT_MODE_INVERT_PIN); //Initialisation du Timer 0 tc_enable(&TCC0); tc_set_cca_interrupt_callback(&TCC0, timer0_tickA); //Création d'un callback qui sera executé quand le timer arrivera à CCA tc_set_ccb_interrupt_callback(&TCC0, timer0_tickB); //Création d'un callback qui sera executé quand le timer arrivera à CCB tc_set_wgm(&TCC0, TC_WG_NORMAL); //Choix du mode du timer0, dans ce cas il comptera jusqu'à sa valeur max et retombera à 0 tc_write_period(&TCC0, 32250); //Définition de la valeur qui déclenchera un reset du timer 0 : 32250 (1 seconde) tc_write_cc(&TCC0, TC_CCA, 3125); //Valeur comparée A : 3125 (100ms) tc_write_cc(&TCC0, TC_CCB, 6250); //Valeur comparée B : 6250 (200ms) tc_set_cca_interrupt_level(&TCC0, TC_INT_LVL_LO); //Activation de l'interruption du timer tc_set_ccb_interrupt_level(&TCC0, TC_INT_LVL_MED); cpu_irq_enable(); //Activation de l'interruption globale tc_write_clock_source(&TCC0, TC_CLKSEL_DIV64_gc); //Activation de l'horloge du timer 0 board_init(); while(1) { } }
#include <asf.h> #define LED_VERTE IOPORT_CREATE_PIN(PORTD, 5) //LED #define Periode 1075 int main (void) { //Initialisation du controleur d'interruptions pmic_init(); //Initialisation de l'horloge sysclk_init(); //Initialisation des entrées/sorties ioport_set_pin_dir(LED_VERTE, IOPORT_DIR_OUTPUT); //Initialisation du Timer 1 tc_enable(&TCD1); tc_set_wgm(&TCD1, TC_WGMODE_SINGLESLOPE_gc); //Choix du mode du timer1 et du mode PWM tc_write_period(&TCD1, Periode); //Définition de la période du PWM tc_write_cc(&TCD1, TC_CCB, Periode/100); //Allume la LED à 1% de la luminosité tc_enable_cc_channels(&TCD1,TC_CCBEN); //Active la sortie CCB du Timer 1 - Donc PD5 cpu_irq_enable(); //Activation de l'interruption globale tc_write_clock_source(&TCD1, TC_CLKSEL_DIV8_gc);//Activation de l'horloge du timer 1 fclksys/8 board_init(); while(1) { //Augemente progressivement la luminosité for(int i=1;i<=100;i++) { tc_write_cc(&TCD1, TC_CCB, Periode/100*i); delay_ms(10); } //Diminue progressivement la luminosité for(int i=100;i>=1;i--) { tc_write_cc(&TCD1, TC_CCB, Periode/100*i); delay_ms(10); } } }
La classe CDC permet d’établir une communication série (RS232) via l'USB du microcontrôleur.
La liaison sera équivalente à une liaison UART avec les contrôles de flux en plus.
Créer un projet ASF et ajouter la librairie USB Device (service) dans le menu déroulant, sélectionner “cdc”
La première chose à faire est de configurer l'horloge. Pour fonctionner, le module USB a besoin d'une horloge de 12 ou 48Mhz. L'oscillateur interne de 32Mhz sera utilisé. En effet sa fréquence peut être réglée entre 30 et 55Mhz grâce à une PLL numérique. Nous allons donc le faire fonctionner à 48Mhz et nous utiliserons une prédiviseur /2 pour fournir une horloge de 24Mhz au CPU.
Editer le fichier conf_clock.h et supprimer toutes les lignes et mettez à la place :
#ifndef CONF_CLOCK_H_INCLUDED #define CONF_CLOCK_H_INCLUDED //Horloge USB #define CONFIG_USBCLK_SOURCE USBCLK_SRC_RCOSC //Choix de l'horloge interne pour l'USB #define CONFIG_OSC_RC32_CAL 48000000UL //Calibration de l'horloge pour qu'elle fonctionne à 48Mhz #define CONFIG_OSC_AUTOCAL_RC32MHZ_REF_OSC OSC_ID_USBSOF //Utilisation d'une calibration automatique lors de la reception de la premiere trame USB //Horloge CPU qui doit être > à 12Mhz pour les applications USB #define CONFIG_SYSCLK_SOURCE SYSCLK_SRC_RC32MHZ //Choix de l'horloge interne (qui fonctionne à 48Mhz) #define CONFIG_SYSCLK_PSADIV SYSCLK_PSADIV_2 //Prescaler A divise la fréquence par 2 = 24Mhz #define CONFIG_SYSCLK_PSBCDIV SYSCLK_PSBCDIV_1_1 //Prescaler B pas de division. Donc le CPU fonctionne à 24Mhz #endif /* CONF_CLOCK_H_INCLUDED */
Ajouter au début du main, les lignes d'initialisation suivantes :
//Initialisation système sysclk_init(); irq_initialize_vectors(); cpu_irq_enable(); board_init();
Nous allons maintenant nous occuper de la configuration USB. Informations qui seront transmises au controleur USB du PC afin de pouvoir identifier notre carte. Ouvrez le fichier conf_usb.f et indiquer les informations suivantes :
#define USB_DEVICE_VENDOR_ID USB_VID_ATMEL //VID ATMEL #define USB_DEVICE_PRODUCT_ID USB_PID_ATMEL_ASF_CDC //PID CDC #define USB_DEVICE_MAJOR_VERSION 1 //Version du périphérique #define USB_DEVICE_MINOR_VERSION 0 #define USB_DEVICE_POWER 100 //Consommation de notre carte #define USB_DEVICE_ATTR USB_CONFIG_ATTR_BUS_POWERED //La carte est alimentée par le bus USB
Chaque fabricant de périphérique achete auprès d'usb.org des plages identifiants uniques qui permettent à votre ordinateur de l'identifier et charger le driver adéquate. C'est identifiant est composé d'un VID vendor ID USB_DEVICE_VENDOR_ID (Numéro d'identification du fabricant) et d'un PID product ID USB_DEVICE_PRODUCT_ID (Numéro d'identification du produit). Si vous souhaitez avoir votre propre VID/PID, il vous en coutera environ 2000$. En attendant, nous allons garder les VID/PID d'Atmel.
La ligne suivante indique quelle version USB utiliser 1,2 etc …
USB_DEVICE_POWER sera la valeur de consommation de courant max de notre carte. Cette information sera transmise au PC afin qu'il adapte sa gestion de courant.
USB_DEVICE_ATTR indique au contrôleur si le périphérique s'alimente via le port USB ou s'il dispose d'une alimentation indépendante. Dans notre cas, il sera alimenté par le port USB.
Puis ajouter la ligne ci dessous dans votre fichier main.c pour démarrer la pile USB
udc_start();
A ce stade, votre PC doit déjà pouvoir détecter votre carte et installer les drivers.
Nous allons voir maintenant comment envoyer et recevoir des données via cette connexion. Aller dans le fichier conf_usb.f et modifier les lignes suivantes :
//! Interface callback definition #define UDI_CDC_ENABLE_EXT(port) callback_cdc_enable() extern bool callback_cdc_enable(void); #define UDI_CDC_DISABLE_EXT(port) callback_cdc_disable() extern void callback_cdc_disable(void);
Les fonctions callback_cdc_enable et callback_cdc_disable seront appelés dès que la connexion sera établie ou rompue.
Vérifier également les lignes suivantes qui definissent le débit de la liaison :
//! Define it when the transfer CDC Device to Host is a low rate (<512000 bauds) //! to reduce CDC buffers size #define UDI_CDC_LOW_RATE //! Default configuration of communication port #define UDI_CDC_DEFAULT_RATE 115200 #define UDI_CDC_DEFAULT_STOPBITS CDC_STOP_BITS_1 #define UDI_CDC_DEFAULT_PARITY CDC_PAR_NONE #define UDI_CDC_DEFAULT_DATABITS 8
Vous pouvez augmenter le baudrate jusqu'à 512 200 bauds. Au delà, il vous suffit de commenter la ligne #define UDI_CDC_LOW_RATE pour aller plus haut. Il faut savoir sur le debit est limité par la vitesse du CPU et par les fonctions utilisés. Atmel fournit un document montrant les limites de fonctionnement. On peut voir figure 6-4 qu'en utilisant les fonctions ReadBuf et WriteBuf qu'il est possible de monter jusqu'à environ 900Kbauds/s.
Dans votre fichier main.c ajouter les deux callback et le petit bout de code suivant qui renvoi “ok 1” dès que vous taper “1” et “error” si vous envoyé n'importe quoi d'autre.
#include <asf.h> static bool flag_autorize_cdc_transfert = false; //Flag d'autorisation de transfert envoyé par l'hote. char ch; //Caractère reçu //Lors de l'établissement de la connexion bool callback_cdc_enable(void) { flag_autorize_cdc_transfert = true; return true; } //Lors de la deconnexion void callback_cdc_disable(void) { flag_autorize_cdc_transfert = false; } int main (void) { //Initialisation système sysclk_init(); irq_initialize_vectors(); cpu_irq_enable(); board_init(); //Démarrage de l'USB udc_start(); while(1) { if (udi_cdc_is_rx_ready() && flag_autorize_cdc_transfert) //Si la transmission est autorisé et si nous avons reçu quelque chose { ch = udi_cdc_getc(); switch(ch) { case '1': udi_cdc_write_buf("ok 1\r\n", 6); break; default : udi_cdc_write_buf("error\r\n", 7); break; }; } } }
Brancher, Compiler, programmer démarrer votre terminal préféré (Hyperterminal, putty, etc …). Indiquer le port COM de votre carte et le débit (115200) :
et taper 1 ou autre chose