L’objectif est de mettre en oeuvre les E/S analogiques de la carte Atmel SAM4S.
Remarque : Pour ce TP, vous aurez besoin de relier un adaptateur USB/RS232 sur le port série UART J7 et d’utiliser un logiciel d’émulation de terminal ($ picocom -b 115200 /dev/ttyUSB0
ou cutecom
sous GNU/Linux ou TeraTerm
sous Windows) sur le port série virtuel pour visualiser les messages de débuggage. On quitte picocom
en utilisant la combinaison de touches Ctrl-A
puis Ctrl-X
.
Les E/S analogiques sont très utilisées dans les systèmes de contrôle industriel et d’acquisition de grandeurs physiques.
Pour acquérir une grandeur physique comme une température, un éclairement lumineux, une masse, etc … il faut un capteur. Un capteur est construit autour d’un transducteur. Un transducteur est un dispositif convertissant un signal physique en un autre, dans notre cas, un signal électrique. Ce signal électrique, image de la grandeur physique à mesurer, est un signal analogique qu’il faudra convertir. En effet, pour traiter ce signal à partir d’un système informatique, il faudra le convertir en signal numérique. C’est le rôle du CAN (ADC). Le principe de la conversion analogique-numérique est étudiée en cours de Sciences Physiques.
Remarque : il est possible d’utiliser des capteurs qui intègre déjà un convertisseur analogique-numérique et même une logique de contrôle permettant d’obtenir un “capteur numérique”. Les sorties de ce type de capteur sont le plus souvent : série TTL, 1-Wire ou I2C (TWI).
Dans l’autre sens, c’est le système informatique qui veut produire un signal analogique (un son par exemple) à partir d’un signal numérique. Il faut dans ce cas un CNA (DAC). Cela peut permettre aussi de commander une charge en gradateur (en faisant varier sa puissance). Voir la notion de triac.
La carte Atmel SAM4S dispose :
Remarque : les E/S ne sont pas toutes laissées disponibles.
On va tout d’abord mettre en oeuvre :
la capteur de température intégré à la carte (sur le canal 15) et le potentiomètre intégré en PB1 (sur le canal 5)
la sortie analogique 0 en PB13
Les fonctionnalités essentielles des E/S fournies par Atmel sont :
Le contrôle des E/S peut être réalisé :
Le fichier /usr/local/asf/sam/utils/cmsis/sam4s/include/sam4s16c.h
fournit les adresses de base des circuit ADC et DAC :
#define ADC ((Adc *)0x40038000U) /**< \brief (ADC ) Base Address */
#define DACC ((Dacc *)0x4003C000U) /**< \brief (DACC ) Base Address */
Pour manipuler le convertisseur analogique-numérique, le fichier /usr/local/asf/sam/utils/cmsis/sam4s/include/component/component_adc.h
déclare une structure Adc
permettant d’accèder à ses registres :
typedef struct {
__O uint32_t ADC_CR; /**< \brief (Adc Offset: 0x00) Control Register */
__IO uint32_t ADC_MR; /**< \brief (Adc Offset: 0x04) Mode Register */
__IO uint32_t ADC_SEQR1; /**< \brief (Adc Offset: 0x08) Channel Sequence Register 1 */
__IO uint32_t ADC_SEQR2; /**< \brief (Adc Offset: 0x0C) Channel Sequence Register 2 */
__O uint32_t ADC_CHER; /**< \brief (Adc Offset: 0x10) Channel Enable Register */
__O uint32_t ADC_CHDR; /**< \brief (Adc Offset: 0x14) Channel Disable Register */
__I uint32_t ADC_CHSR; /**< \brief (Adc Offset: 0x18) Channel Status Register */
__I uint32_t Reserved1[1];
__I uint32_t ADC_LCDR; /**< \brief (Adc Offset: 0x20) Last Converted Data Register */
__O uint32_t ADC_IER; /**< \brief (Adc Offset: 0x24) Interrupt Enable Register */
__O uint32_t ADC_IDR; /**< \brief (Adc Offset: 0x28) Interrupt Disable Register */
__I uint32_t ADC_IMR; /**< \brief (Adc Offset: 0x2C) Interrupt Mask Register */
__I uint32_t ADC_ISR; /**< \brief (Adc Offset: 0x30) Interrupt Status Register */
__I uint32_t Reserved2[2];
__I uint32_t ADC_OVER; /**< \brief (Adc Offset: 0x3C) Overrun Status Register */
__IO uint32_t ADC_EMR; /**< \brief (Adc Offset: 0x40) Extended Mode Register */
__IO uint32_t ADC_CWR; /**< \brief (Adc Offset: 0x44) Compare Window Register */
__IO uint32_t ADC_CGR; /**< \brief (Adc Offset: 0x48) Channel Gain Register */
__IO uint32_t ADC_COR; /**< \brief (Adc Offset: 0x4C) Channel Offset Register */
__I uint32_t ADC_CDR[16]; /**< \brief (Adc Offset: 0x50) Channel Data Register */
__I uint32_t Reserved3[1];
__IO uint32_t ADC_ACR; /**< \brief (Adc Offset: 0x94) Analog Control Register */
__I uint32_t Reserved4[19];
__IO uint32_t ADC_WPMR; /**< \brief (Adc Offset: 0xE4) Write Protect Mode Register */
__I uint32_t ADC_WPSR; /**< \brief (Adc Offset: 0xE8) Write Protect Status Register */
__I uint32_t Reserved5[5];
__IO uint32_t ADC_RPR; /**< \brief (Adc Offset: 0x100) Receive Pointer Register */
__IO uint32_t ADC_RCR; /**< \brief (Adc Offset: 0x104) Receive Counter Register */
__I uint32_t Reserved6[2];
__IO uint32_t ADC_RNPR; /**< \brief (Adc Offset: 0x110) Receive Next Pointer Register */
__IO uint32_t ADC_RNCR; /**< \brief (Adc Offset: 0x114) Receive Next Counter Register */
__I uint32_t Reserved7[2];
__O uint32_t ADC_PTCR; /**< \brief (Adc Offset: 0x120) Transfer Control Register */
__I uint32_t ADC_PTSR; /**< \brief (Adc Offset: 0x124) Transfer Status Register */
} Adc;
L’affectation des E/S analogiques pour la carte Atmel SAM4S sont définies dans le fichier header /usr/local/asf/sam/utils/cmsis/sam4s/include/pio/pio_sam4s16c.h
:
/* ========== Pio definition for ADC peripheral ========== */
#define PIO_PA17X1_AD0 (1u << 17) /**< \brief Adc signal: AD0 */
#define PIO_PA18X1_AD1 (1u << 18) /**< \brief Adc signal: AD1 */
#define PIO_PC13X1_AD10 (1u << 13) /**< \brief Adc signal: AD10 */
#define PIO_PC15X1_AD11 (1u << 15) /**< \brief Adc signal: AD11 */
#define PIO_PC12X1_AD12 (1u << 12) /**< \brief Adc signal: AD12 */
#define PIO_PC29X1_AD13 (1u << 29) /**< \brief Adc signal: AD13 */
#define PIO_PC30X1_AD14 (1u << 30) /**< \brief Adc signal: AD14 */
#define PIO_PA19X1_AD2 (1u << 19) /**< \brief Adc signal: AD2/WKUP9 */
#define PIO_PA19X1_WKUP9 (1u << 19) /**< \brief Adc signal: AD2/WKUP9 */
#define PIO_PA20X1_AD3 (1u << 20) /**< \brief Adc signal: AD3/WKUP10 */
#define PIO_PA20X1_WKUP10 (1u << 20) /**< \brief Adc signal: AD3/WKUP10 */
#define PIO_PB0X1_AD4 (1u << 0) /**< \brief Adc signal: AD4/RTCOUT0 */
#define PIO_PB0X1_RTCOUT0 (1u << 0) /**< \brief Adc signal: AD4/RTCOUT0 */
#define PIO_PB1X1_AD5 (1u << 1) /**< \brief Adc signal: AD5/RTCOUT1 */
#define PIO_PB1X1_RTCOUT1 (1u << 1) /**< \brief Adc signal: AD5/RTCOUT1 */
#define PIO_PB2X1_AD6 (1u << 2) /**< \brief Adc signal: AD6/WKUP12 */
#define PIO_PB2X1_WKUP12 (1u << 2) /**< \brief Adc signal: AD6/WKUP12 */
#define PIO_PB3X1_AD7 (1u << 3) /**< \brief Adc signal: AD7 */
#define PIO_PA21X1_AD8 (1u << 21) /**< \brief Adc signal: AD8 */
#define PIO_PA22X1_AD9 (1u << 22) /**< \brief Adc signal: AD9 */
#define PIO_PA8B_ADTRG (1u << 8) /**< \brief Adc signal: ADTRG */
/* ========== Pio definition for DACC peripheral ========== */
#define PIO_PB13X1_DAC0 (1u << 13) /**< \brief Dacc signal: DAC0 */
#define PIO_PB14X1_DAC1 (1u << 14) /**< \brief Dacc signal: DAC1 */
#define PIO_PA2C_DATRG (1u << 2) /**< \brief Dacc signal: DATRG */
Et celles des registres dans le fichier header /usr/local/asf/sam/utils/cmsis/sam4s/include/instance/instance_adc.h
.
Remarque : il vous faudra inclure les fichiers d’en-têtes (header) asf.h
, adc.h
et dacc.h
.
La conversion d’une entrée analogique peut être déclenchée logiciellement (software trigger) ou matériellement (hardware trigger) :
adc_start()
). En écrivant un 1 dans le bit SWRST, on arrête la conversion en provoquant un reset du circuit ADC.Les 16 canaux analogiques sont multiplexés par des lignes du PIO. Les entrées sont activées en écrivant dans le registre CER (Channel Enable Register). Elles peuvent être configurées en entrée simple (bit DIFF à 0 dans le registre ADC_COR) ou en mode différentielle (bit DIFF à 1 dans ADC_COR).
Il est possible de configurer un gain d’amplification de 1/2, 1, 2 ou 4 par les bits GAINx du registre ADC_CGR (Channel Gain register) pour chaque canal. Avec le bit ANACH du registre MR, on peut choisir d’appliquer la configuration à l’ensemble des canaux ou pas.
Il reste à configurer les 2 temps de fonctionnement de l’ADC :
La configuration des temps est basée sur la fréquence d’horloge du circuit. L’ADC utilise l’horloge ADCCLK pour cadencer ces conversions. La fréquence d’horloge est sélectionnée par le champ PRESCAL du registre MR. Elle est comprise entre fréquence d’horloge/2 (avec PRESCAL = 0) et fréquence d’horloge/512 (avec PRESCAL = 255 (0xFF)).
Le mode de fonctionnement de l’ADC se configure donc à partir du registre MR :
Quand une conversion est terminée, la valeur convertie est stockée dans le registre ADC_CDRx (Channel Data register) où x correspond au canal et dans le registre ADC_LCDR (Last Converted Data register) pour toujours conserver la dernière valeur convertie.
Les bits EOCx (End Of Conversion) et DRDY (Data Ready) du registre ADC_ISR (Interrupt Status register) sont alors mis à 1. Ils peuvent servir à mettre en oeuvre un gestion des interruptions. D’autres interruptions sont disponibles (buffer plein, …).
Lire le registre ADC_CDRx (Channel Data register) provoque la remise à zéro du bit EOC du canal correspondant. Et lire le registre ADC_LCDR effacera alors le bit DRDY.
Les registres du CAN/CNA sont
ADC Control Register : 0x40038000
DACC Control Register : 0x4003C000
Pour arrêter/démarrer une conversion CAN, il faut écrire dans le registre Control Register :
// Démarre la conversion
ADC->ADC_CR = ADC_CR_START; // bit 1 START soit (1 << 1)
// ...
// Stoppe la conversion
ADC->ADC_CR = ADC_CR_SWRST; // bit 0 SWRST soit (1 << 0)
Remarque : le bit 3 AUTOCAL de ce registre permet l’autocalibration.
Pour lire une valeur convertie sur 12 bits, il suffit de lire le registre Channel Data Register CDRx où x représente le numéro de canal :
uint32_t ul_data = 0;
// lit la valeur du canal 15 (capteur de température interne)
ul_data = *(ADC->ADC_CDR + 15);
// ou la dernière valeur convertie
//ul_data = ADC->ADC_LCDR;
Attention : avant de démarrer une conversion et de lire une valeur, il faut INITIALISER le CAN (ADC) !
La partie la plus complexe est l’initialisation et la gestion des différents modes du CAN (ADC). La lecture et l’étude de la documentation est indispensable.
Documentation : Chapitre 42 Analog-to-Digital Converter (ADC) page 1083 et Chapitre 43 Digital-to-Analog Converter Controller (DACC) page 1121 du atmel-11100-32-bit-cortex-m4-microcontroller-sam4s_datasheet.pdf
Pour initialiser le CAN (ADC) pour le capteur de température, on peut réaliser la séquence suivante :
// Active l'horloge associé à l'ADC
pmc_enable_periph_clk(ID_ADC);
// Réinitialise le contrôleur
ADC->ADC_CR = ADC_CR_SWRST;
// Réinitialise le registe de configuration du mode
ADC->ADC_MR = 0;
// Configure la fréquence du convertisseur (valeur PRESCAL) à partir de l'horloge
/*
* Formula: ADCClock = MCK / ( (PRESCAL+1) * 2 )
* For example, MCK = 64MHZ, PRESCAL = 4, then:
* ADCClock = 64 / ((4+1) * 2) = 6.4MHz;
*
* Formula:
* Startup Time = startup value / ADCClock
* Startup time = 64 / 6.4MHz = 10 us
*/
uint32_t ul_prescal = sysclk_get_cpu_hz() / (2 * 6400000) - 1;
ADC->ADC_MR |= ADC_MR_PRESCAL(ul_prescal) | ADC_STARTUP_TIME_4;
// Active le canal 15 du capteur de température
//ADC->ADC_CHER = ADC_CHER_CH15;
ADC->ADC_CHER = 1 << 15;
// Active le capteur de température
ADC->ADC_ACR |= ADC_ACR_TSON;
// Démarre la conversion
ADC->ADC_CR = ADC_CR_START; //(0x1u << 1)
Pour simplifier la programmation directe des ADC/DAC, la bibliothèque logicielle ASF fournit des fonctions de contrôle : http://asf.atmel.com/docs/latest/sam4s/html/group__sam__drivers__adc__group.html et http://asf.atmel.com/docs/latest/sam4s/html/group__sam__drivers__dacc__group.html.
Pour initialiser le CAN (ADC) pour le capteur de température, on peut réaliser la séquence suivante :
// Active l'horloge associé à l'ADC
pmc_enable_periph_clk(ID_ADC);
// Initialise l'ADC
adc_init(ADC, sysclk_get_cpu_hz(), 6400000, ADC_STARTUP_TIME_4);
// Vérifie la configuration
//adc_check(ADC, sysclk_get_cpu_hz());
// Active le canal 15 du capteur de température
adc_enable_channel(ADC, ADC_TEMPERATURE_SENSOR);
// Active le capteur de température
adc_enable_ts(ADC);
// Démarre la conversion
adc_start(ADC);
Pour lire une valeur convertie sur 12 bits, il suffit de lire le canal correspondant :
uint32_t ul_data = 0;
ul_data = adc_get_channel_value(ADC, ADC_TEMPERATURE_SENSOR);
On désire acquérir la température à partir du capteur analogique intégré à la carte Atmel SAM4S et relié sur l’entrée 15.
On crée une première version du programme principal qui utilise les directement registres de l’ADC :
$ vim main.c
// Acquérir une tension analogique sur la carte Atmel SAM4S
// version 1 : à partir des registres de l'ADC
#include <asf.h>
#include <adc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STRING_EOL "\r"
#define STRING_HEADER \
"-- Exemple ADC - "BOARD_NAME" --\r\n" \
"-- "__DATE__" "__TIME__" --"STRING_EOL
/** ADC */
/** Tension de référence en mv. */
#define MVOLT_REF (3300)
/** Tension de référence en v. */
#define VOLT_REF (3.3)
/** Résolution 12-bits */
#define MAX_DIGITAL (4095)
/**
* Mes variables globales
*/
volatile uint32_t g_ul_ms_ticks = 0;
uint32_t data = 0;
/**
* Mes fonctions
*/
void print(uint32_t ul_data, bool temperature);
static void configure_uart(void);
static void my_init(void);
static void mdelay(uint32_t ul_dly_ticks);
/**
* \brief Programme principal
*
*/
int main (void)
{
uint32_t ul_data = 0;
my_init();
while (1)
{
// Active l'horloge associé à l'ADC
pmc_enable_periph_clk(ID_ADC);
// Réinitialise le contrôleur
ADC->ADC_CR = ADC_CR_SWRST;
// Réinitialise Reset Mode
ADC->ADC_MR = 0;
// Initialise l'ADC
/*
* Formula: ADCClock = MCK / ( (PRESCAL+1) * 2 )
* For example, MCK = 64MHZ, PRESCAL = 4, then:
* ADCClock = 64 / ((4+1) * 2) = 6.4MHz;
*
* Startup Time = startup value / ADCClock
* Startup time = 64 / 6.4MHz = 10 us
*/
uint32_t ul_prescal = sysclk_get_cpu_hz() / (2 * 6400000) - 1;
ADC->ADC_MR |= ADC_MR_PRESCAL(ul_prescal) | ADC_STARTUP_TIME_4;
// Active le canal 15 du capteur de température
ADC->ADC_CHER = ADC_CHER_CH15;
//ADC->ADC_CHER = 1 << 15;
// Active le capteur de température
ADC->ADC_ACR |= ADC_ACR_TSON;
// Démarre la conversion
ADC->ADC_CR = ADC_CR_START; //(0x1u << 1)
// Lit la valeur de la conversion d'un canal (ici le 15)
ul_data = *(ADC->ADC_CDR + 15);
print(ul_data, 1);
printf("\r\n");
mdelay(500); /* Attend 500ms */
}
return 0;
}
/**
* \brief Affichage
*
*/
void print(uint32_t ul_data, bool temperature)
{
float f_tension = 0;
uint32_t ul_tension;
float f_temp = 0;
char tampon[128]; // pour l'affichage
// La donnée brute sur 12 bits
printf("Data : %ld (0x%04X)\r\n", ul_data, (unsigned int)(ul_data & 0x00000FFF));
// Conversion en tension
f_tension = ((float)ul_data * VOLT_REF) / (float)MAX_DIGITAL;
// Conversion en chaîne de caractères pour l'affichage
// Affichage en V
sprintf(tampon, "%.2f", f_tension);
printf("Tension : %s V\r\n", tampon);
// Affichage en mV
ul_tension = (uint32_t)(f_tension*1000.);
printf("Tension : %ld mV\r\n", ul_tension);
if(temperature == 1)
{
// Conversion en température
/* Utilise une multiplication (*0.21276) au lieu d'une division (/4.7). */
f_temp = (float)(ul_tension - 1440) * 0.21276 + 27.0;
sprintf(tampon, "%.1f", f_temp);
printf("Temperature : %s Celcius\r\n", tampon);
}
}
/* ... */
Code source : tp2-temperature.zip
Test :
$ make
$ ./run.sh xxx_flash.elf
On crée une deuxième version qui utilise l’API ADC de la bibliothèque ASF.
On améliore le programme en intégrant une gestion d’interruption basée sur la fin de conversion. Il faut alors définir le gestionnaire d’interruption ADC_Handler()
.
$ vim main.c
// Acquérir une tension analogique sur la carte Atmel SAM4S
// version 2 : à partir des fonctions ASF
#include <asf.h>
#include <adc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STRING_EOL "\r"
#define STRING_HEADER \
"-- Exemple ADC - "BOARD_NAME" --\r\n" \
"-- "__DATE__" "__TIME__" --"STRING_EOL
/** ADC */
/** Tension de référence en mv. */
#define MVOLT_REF (3300)
/** Tension de référence en v. */
#define VOLT_REF (3.3)
/** Résolution 12-bits */
#define MAX_DIGITAL (4095)
/**
* Mes variables globales
*/
volatile uint32_t g_ul_ms_ticks = 0;
uint32_t data = 0;
/**
* Mes fonctions
*/
void print(uint32_t ul_data, bool temperature);
static void configure_uart(void);
static void my_init(void);
static void mdelay(uint32_t ul_dly_ticks);
/**
* \brief Programme principal
*
*/
int main (void)
{
uint32_t ul_data = 0;
my_init();
while (1)
{
// Version 2 :
// Active l'horloge associé à l'ADC
pmc_enable_periph_clk(ID_ADC);
// Initialise l'ADC
// Formula: ADCClock = MCK / ( (PRESCAL+1) * 2 )
// For example, MCK = 64MHZ, PRESCAL = 4, then:
// ADCClock = 64 / ((4+1) * 2) = 6.4MHz;
// Startup Time = startup value / ADCClock
// Startup time = 64 / 6.4MHz = 10 us
adc_init(ADC, sysclk_get_cpu_hz(), 6400000, ADC_STARTUP_TIME_4);
// Active le canal 15 du capteur de température
adc_enable_channel(ADC, ADC_TEMPERATURE_SENSOR);
// Active le capteur de température
adc_enable_ts(ADC);
// Active les interruption de l'ADC (cf. ADC_Handler)
NVIC_EnableIRQ(ADC_IRQn);
// Démarre la conversion
adc_start(ADC);
// Active l'interruption
//adc_enable_interrupt(ADC, ADC_ISR_DRDY); // (0x1u << 24)
adc_enable_interrupt(ADC, ADC_ISR_EOC15);
printf("\r\n");
mdelay(500); /* Attend 500ms */
}
return 0;
}
/**
* \brief Le gestionnaire d'interruption de l'ADC
*/
void ADC_Handler(void)
{
// Vérifie l'état de la conversion canal 15
if ((adc_get_status(ADC) & ADC_ISR_EOC15) == ADC_ISR_EOC15)
{
// Vérifie que la donnée est prête à être lue
if (((adc_get_status(ADC) & ADC_ISR_DRDY) == ADC_ISR_DRDY))
{
// Lit la valeur de la conversion d'un canal (ici le 15)
data = adc_get_channel_value(ADC, ADC_TEMPERATURE_SENSOR);
// Lit la dernière valeur
//data = adc_get_latest_value(ADC);
print(data, 1);
}
}
}
Attention : Il faut ajouter sam/drivers/adc/adc.c
dans la liste des fichiers à compiler (variable CSRCS
du fichier config.mk
) et sam/drivers/adc
dans la liste des chemins des fichiers headers (variable INC_PATH
du fichier config.mk
).
Remarque : la fonction print()
permet d’afficher dans la console de débugage. Si l’argument temperature
est à 1, elle convertit ul_data
et affiche la température correspondante. La formule de conversion de la température est décrite à la page 1183 du datasheet. Le code source est fourni en Annexe.
À vous de jouer :
Le potentiomètre est relié sur l’entrée PB1 (canal 5).
Écrire les deux versions du programme de test.
On désire piloter la sortie analogique n°0 (PB13) de la carte Atmel SAM4S.
Attention : la tension de sortie est comprise entre (1/6) × VADVREF et (5/6) × VADVREF. Pour VADVREF = 3.3V, cela donne une plage de 0.55V et 2.75V.
On crée une version qui utilise l’API DACC de la bibliothèque ASF :
$ vim main.c
// Produire un signal analogique sur la carte Atmel SAM4S
// version : à partir des fonctions ASF
#include <asf.h>
#include <dacc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STRING_EOL "\r"
#define STRING_HEADER \
"-- Exemple DAC - "BOARD_NAME" --\r\n" \
"-- "__DATE__" "__TIME__" --"STRING_EOL
/** DAC */
#define DACC_CHANNEL 0 // (PB13)
#define DACC_ANALOG_CONTROL (DACC_ACR_IBCTLCH0(0x02) | DACC_ACR_IBCTLCH1(0x02) | DACC_ACR_IBCTLDACCORE(0x01))
/** Tension de référence en v. */
#define VOLT_REF (3.3)
/** Résolution 12-bits */
#define MAX_DIGITAL (4095)
/**
* \brief Programme principal
*
*/
int main (void)
{
uint32_t status;
float f_tension;
uint32_t ul_data = 0;
my_init();
while (1)
{
// Version 2 :
// Active l'horloge associé au DACC
sysclk_enable_peripheral_clock(ID_DACC);
// Réinitialise les registres
dacc_reset(DACC);
// Configure le mode de transfert
// (0 = Half-word transfer [16bits -> une donnée] ou 1 = Word transfer [32bits -> 2 données])
dacc_set_transfer_mode(DACC, 0);
// Initialise le DAC
/* Timing:
* refresh - 0x08 (1024*8 dacc clocks)
* max speed mode - 0 (disabled)
* startup time - 0x10 (1024 dacc clocks)
*/
dacc_set_timing(DACC, 0x08, 0, 0x10);
// Désactive le TAG et sélectionne la sortie analogique
// la sélection du canal se fait par le registre MR (USER_SEL)
// et non par les bits 12 et 13 de la donnée (mode TAG)
dacc_set_channel_selection(DACC, DACC_CHANNEL);
// Active la sortie analogique
dacc_enable_channel(DACC, DACC_CHANNEL);
// Controle les performances par rapport à la consommation
dacc_set_analog_control(DACC, DACC_ANALOG_CONTROL);
f_tension = 1.4;
// Conversion en data
ul_data = (uint32_t)(((f_tension * (float)MAX_DIGITAL) / VOLT_REF) - (0.55 * (float)MAX_DIGITAL / VOLT_REF));
// La donnée brute sur 12 bits
printf("Data : %ld (0x%04X)\r\n", ul_data, (unsigned int)(ul_data & 0x00000FFF));
status = dacc_get_interrupt_status(DACC);
// Écrit une valeur sur la sortie analogique
if ((status & DACC_ISR_TXRDY) == DACC_ISR_TXRDY)
{
dacc_write_conversion_data(DACC, ul_data);
}
printf("\r\n");
mdelay(500); /* Attend 500ms */
}
return 0;
}
Attention : Il faut ajouter sam/drivers/dacc/dacc.c
dans la liste des fichiers à compiler (variable CSRCS
du fichier config.mk
) et sam/drivers/dacc
dans la liste des chemins des fichiers headers (variable INC_PATH
du fichier config.mk
).
Test :
$ make
$ ./run.sh xxx_flash.elf
À vous de jouer :
Cabler un montage exploitant une entrée et une sortie analogiques.
Écrire un programme de démonstration du montage réalisé.
Vous devez être capable de répondre aux questions suivantes :
Définir une E/S analogique, le rôle d’un CAN/CNA.
Quel est le rôle d’un CAN ? d’un CNA ?
Définir le terme de transducteur.
Décrire la méthode de programmation d’une E/S analogique.
Quel est le nom du registre qui permet de sélectionner un canal ?
Décrire le principe de gestion d’une interruption matérielle ?
Qu’est-ce que le quantum dans une conversion analogique-numérique ?
Combien de tensions analogiques différentes peut-on obtenir avec un CAN 12 bits ?
En électronique numérique, qu’est-ce qu’une horloge ?
Qu’est-ce qu’un trigger ?
La fonction my_init()
fournie permet d’initialiser la carte (board_init()
), le système d’horloge (sysclk_init()
) et le port série UART (configure_uart()
). Une fonction mdelay()
fournit un moyen de réaliser des temporisations d’attente en millisecondes.
#define STRING_EOL "\r"
#define STRING_HEADER \
"-- Exemple LED Bleue - "BOARD_NAME" --\r\n" \
"-- "__DATE__" "__TIME__" --"STRING_EOL
/** UART Interface */
#define CONF_UART CONSOLE_UART
#define CONF_UART_BAUDRATE 115200
#define CONF_UART_PARITY UART_MR_PAR_NO
#define CONF_UART_CHAR_LENGTH US_MR_CHRL_8_BIT
#define CONF_UART_STOP_BITS US_MR_NBSTOP_1_BIT
volatile uint32_t g_ul_ms_ticks = 0;
static void my_init(void);
static void configure_uart(void);
static void mdelay(uint32_t ul_dly_ticks);
/**
* \brief Affichage et conversion
*
*/
void print(uint32_t ul_data, bool temperature)
{
float f_tension = 0;
uint32_t ul_tension;
float f_temp = 0;
char tampon[128]; // pour l'affichage
// La donnée brute sur 12 bits
printf("Data : %ld (0x%04X)\r\n", ul_data, (unsigned int)(ul_data & 0x00000FFF));
// Conversion en tension
f_tension = ((float)ul_data * VOLT_REF) / (float)MAX_DIGITAL;
// Conversion d'un float en chaîne de caractères pour l'affichage
// Affichage en V
sprintf(tampon, "%.2f", f_tension);
printf("Tension : %s V\r\n", tampon);
// Affichage en mV
ul_tension = (uint32_t)(f_tension*1000.);
printf("Tension : %ld mV\r\n", ul_tension);
if(temperature == 1)
{
// Conversion en température
/* Utilise une multiplication (*0.21276) au lieu d'une division (/4.7). */
f_temp = (float)(ul_tension - 1440) * 0.21276 + 27.0;
sprintf(tampon, "%.1f", f_temp);
printf("Temperature : %s Celcius\r\n", tampon);
}
}
/**
* Initialise
*/
static void my_init(void)
{
board_init();
sysclk_init();
/* Initialise le port uart */
configure_uart();
/* Envoie une chaîne de caractères */
puts(STRING_HEADER);
/* Configure les tics à 1 ms */
if (SysTick_Config(sysclk_get_cpu_hz() / 1000))
{
puts("Erreur systick configuration\r");
while (1);
}
}
/**
* \brief Gestionnaire d'évènement pour les tics d'interruptions système
*
* Incremente le compteur g_ul_ms_ticks
*
*/
void SysTick_Handler(void)
{
g_ul_ms_ticks++;
}
/**
* \brief Temporisation d'attente en millisecondes (basée sur les tics systèmes du microcontrôleur)
*
* \param ul_dly_ticks temps d'attente en millisecondes
*/
static void mdelay(uint32_t ul_dly_ticks)
{
uint32_t ul_cur_ticks;
ul_cur_ticks = g_ul_ms_ticks;
while ((g_ul_ms_ticks - ul_cur_ticks) < ul_dly_ticks);
}
/**
* Configure UART.
*/
static void configure_uart(void)
{
const usart_serial_options_t uart_serial_options =
{
.baudrate = CONF_UART_BAUDRATE,
.charlength = CONF_UART_CHAR_LENGTH,
.paritytype = CONF_UART_PARITY,
.stopbits = CONF_UART_STOP_BITS,
};
sysclk_enable_peripheral_clock(CONSOLE_UART_ID);
stdio_serial_init(CONF_UART, &uart_serial_options);
}