Atmel SAM4S

Atmel SAM4S sous Linux - TP n°3 : la communication série RS232

L’objectif est de mettre en oeuvre une communication série utilisant la liaison RS232 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. On quitte picocom en utilisant la combinaison de touches Ctrl-A puis Ctrl-X.

Ressources

Introduction

La transmission série est un mode de transmission de données dans lequel les éléments d’information se succèdent les uns après les autres sur une seule voie entre deux points. Elle s’oppose à la transmission parallèle, qui transmet simultanément les éléments d’information sur plusieurs voies.

La transmission série domine dès que les composants ou périphériques à relier sont à quelque distance. L’ensemble des télécommunications s’établit sur des liaisons série.

La transmission série est très présente dans le monde industriel :

  • pour relier des capteurs/actionneurs (sensor bus) ou des composants de bas niveau, on utilise des technologies comme le bus TWI/I2C, 1-Wire, AS-i, …

  • pour relier des périphériques (appareils divers, système de commande, …), on utilise des bus de terrain (field bus) comme le bus CAN, DMX, les liaisons RS232 ou RS485, …

La transmission série se fait généralement par trames et nécessite la mise en oeuvre de protocole.

Remarque : Communiquer consiste à transmettre des informations, mais tant que les interlocuteurs ne lui ont pas attribué un sens, il ne s’agit que de données et pas d’information. Les interlocuteurs doivent donc non seulement parler un langage commun mais aussi maîtriser des règles minimales d’émission et de réception des données. C’est le rôle d’un protocole de s’assurer de tout cela.

Le port série RS232

La carte Atmel SAM4S dispose de deux circuits de transmission série :

  • UART (Universal Asynchronous Receiver Transmitter)
  • USART (Universal Synchronous Asynchronous Receiver Transceiver) permettant les modes de fonctionnement des interfaces sur RS485, bus SPI, carte à puce ISO 7816, émetteurs-récepteurs infrarouges et connexion aux ports de modem RS232

Documentation : Chapitre 35 Universal Asynchronous Receiver Transmitter (UART) page 761 du atmel-11100-32-bit-cortex-m4-microcontroller-sam4s_datasheet.pdf et Chapitre 36 Universal Synchronous Asynchronous Receiver Transceiver (USART) page 779 du atmel-11100-32-bit-cortex-m4-microcontroller-sam4s_datasheet.pdf

Les fonctions de la bibliotèque logicielle ASF

Pour simplifier la programmation directe de l’UART, la bibliothèque logicielle ASF fournit des fonctions de contrôle : http://asf.atmel.com/docs/latest/sam4s/html/group__sam__drivers__uart__group.html et http://asf.atmel.com/docs/latest/sam4s/html/group__sam__drivers__usart__group.html.

Manipulations

Transmission sur le port UART

On a déjà utilisé la liaison série RS232 (UART) à des fins d’affichage de messages de débugage (fonctions puts() et printf()) :

En effet les fonctions de la stdio utilisent les fonctions de bas niveau read()et write() qui ont été réécrites pour recevoir ou envoyer des caractères sur le port série (cf. /usr/local/asf/common/utils/stdio/).

On peut donc soit utiliser des fonctions standards (puts() ou printf() et scanf()) ou celles de l’API UART pour émettre et recevoir des caractères :

  • uart_read() et uart_write()
  • uart_is_rx_ready() et uart_is_tx_ready()

On crée ce programme de test :

$ vim main.c
#include <asf.h>

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

int main (void)
{   
    board_init();
    
    sysclk_init();
    
    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);
    
    uint8_t fini = 0;    
    uint8_t prompt[] = "\r> ";
    uint8_t touche = 0;
    
    printf("%s", (char *)prompt);
    
    // echo et touche Entree pour quitter
    while(!fini) 
    {
        scanf("%c", &touche);
        if (touche == '\r') 
        {            
            puts("\r");
            fini = 1;
        }
        else
        {
            printf("%c", touche);
        }
    }
    
    puts("End.\r\n");
    
    return 0;
}

Code source : tp3-uart.zip

Test :

$ make
$ ./run.sh xxx_flash.elf 

À vous de jouer :

  1. Tester la première version du programme.

  2. Écrire une version utilisant les fonctions de l’API UART pour effectuer un echo des caractères reçus.

Transmission sur le port USART

La carte Atmel SAM4S fournit un circuit USART (Universal Synchronous Asynchronous Receiver Transceiver) qui permet de gérer les modes de fonctionnement des interfaces sur RS485, bus SPI, carte à puce ISO 7816, émetteurs-récepteurs infrarouges et connexion aux ports de modem RS232.

Ici, on va utiliser le port UART1 en mode standard RS232 physiquement disponible sur le connecteur DB9 du kit de développement SAM4S-EK.

On utilisera les lignes TXD1 (PA22) et RXD1 (PA21_232) sans contrôle de flux matériel (RTS/CTS).

Attention : il faudra activer par un niveau bas la ligne /EN (PA23) et placer le cavalier JP31 en position 1-2.

Pour cette manipulation, il vous faudra relier un adaptateur USB/RS232 sur le port série USART J5 et utiliser un logiciel d’émulation de terminal (picocom ou cutecom sous GNU/Linux ou TeraTerm sous Windows) sur le port série virtuel. On quitte picocom en utilisant la combinaison de touches Ctrl-A puis Ctrl-X.

On commencera par configurer les lignes du PIOA :

gpio_configure_pin(PIN_USART1_RXD_IDX, PIN_USART1_RXD_FLAGS);
gpio_configure_pin(PIN_USART1_TXD_IDX, PIN_USART1_TXD_FLAGS);    
gpio_configure_pin(PIN_USART1_EN_IDX, PIN_USART1_EN_FLAGS);
gpio_set_pin_low(PIN_USART1_EN_IDX);

Puis on initialisera le port UART1 pour une communication RS232 :

#define CONF_USART_BAUDRATE      115200

const sam_usart_opt_t usart_options = {
    CONF_USART_BAUDRATE,
    US_MR_CHRL_8_BIT,
    US_MR_PAR_NO,
    US_MR_NBSTOP_1_BIT,
    US_MR_CHMODE_NORMAL,
    0 // ce champ n'est utile qu'en mode IrDA
};

sysclk_enable_peripheral_clock(ID_USART1);    

// Configure l'USART
usart_init_rs232(USART1, &usart_options, sysclk_get_cpu_hz());

Remarque : les paramètres de configuration sont définis dans le fichier /usr/local/asf/sam/utils/cmsis/sam4s/include/component/component_uart.h. Les fonctions et structures utilisées sont déclarées dans le fichier /usr/local/asf/sam/drivers/usart/usart.h.

Ensuite, il suffit d’utiliser des fonctions de l’API USART pour émettre et recevoir des caractères :

  • usart_putchar() et usart_getchar()
  • usart_write(), usart_write_line() et usart_read()
  • usart_serial_write_packet() et usart_serial_read_packet()

On écrit un premier programme de test du port USART1 :

$ vim main.c
#include <asf.h>
#include <string.h>

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

/** USART Interface */
#define CONF_USART               USART1

/**
 *  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);
}

/**
 *  Configure USART1 RS232.
 */
static void configure_usart_rs232(void)
{
    const sam_usart_opt_t usart_options = {
        CONF_USART_BAUDRATE,
        US_MR_CHRL_8_BIT,
        US_MR_PAR_NO,
        US_MR_NBSTOP_1_BIT,
        US_MR_CHMODE_NORMAL,
        0 // ce champ n'est utile qu'en mode IrDA
    };

    sysclk_enable_peripheral_clock(ID_USART1);    

    // Configure l'USART
    usart_init_rs232(USART1, &usart_options, sysclk_get_cpu_hz());
}

int main (void)
{   
    board_init();

    /* Configure USART RXD pin & TXD pin */
    gpio_configure_pin(PIN_USART1_RXD_IDX, PIN_USART1_RXD_FLAGS);
    gpio_configure_pin(PIN_USART1_TXD_IDX, PIN_USART1_TXD_FLAGS);    
    gpio_configure_pin(PIN_USART1_EN_IDX, PIN_USART1_EN_FLAGS);
    gpio_set_pin_low(PIN_USART1_EN_IDX);
    
    sysclk_init();

    /* Initialise le port uart */
    configure_uart();
    
    /* Initialise le port usart */
    configure_usart_rs232();
   
    puts("Test1 RS232\r\n"); 
    uint32_t data = '$';
    uint32_t status;
    
    status = usart_write(USART1, data);
    //printf("usart_write() %ld\r\n", status);   
    usart_write_line(USART1, "Ok");
    usart_write_line(USART1, "\r\n");
    
    puts("Test2 RS232\r\n"); 
    uint8_t fini = 0;
    uint8_t tx_len = 0;
    uint8_t tx_buf[] = "\n\rHello world ! : ";
    uint8_t rx_buf[] = "0";
    
    tx_len = (uint8_t)strlen((char *)tx_buf);
    usart_serial_write_packet(USART1, tx_buf, tx_len);
            
    // echo et touche Entree pour quitter
    while(!fini) 
    {
        usart_serial_read_packet(USART1, rx_buf, 1);        
        if (rx_buf[0] == '\r') 
        {            
            usart_serial_write_packet(USART1, tx_buf, 2);
            fini = 1;
        } 
        else 
        {
            printf("echo_usart() read : %c 0x%02X\r\n", rx_buf[0], rx_buf[0]);
            usart_serial_write_packet(USART1, rx_buf, 1);
        }
    }
    
    puts("End.\r\n");

    return 0;
}

Pour la réception de caractères, il semble judicieux d’utiliser un fonctionnement par interruption.

Pour cela à l’initialisation, on réalisera en plus la séquence suivante :

#define ALL_INTERRUPT_MASK  0xffffffff

// Désactive toutes les interruptions
usart_disable_interrupt(USART1, ALL_INTERRUPT_MASK);

// Active l'émission et la réception
usart_enable_tx(USART1);
usart_enable_rx(USART1);

// Active les interruptions sur l'USART
NVIC_EnableIRQ(USART1_IRQn);

// Active l'interruption RXRDY
usart_enable_interrupt(USART1, US_IER_RXRDY);

Il suffit ensuite de définir le gestionnaire d’interruption USART1_Handler() :

static uint32_t read_buffer = 0;

void USART1_Handler(void)
{
    uint32_t ul_status;    

    // Lit l'état de l'USART
    ul_status = usart_get_status(USART1);
    //printf("status : 0x%04X\r\n", (uint8_t)ul_status);
 
    // caractère reçu ?
    if (ul_status & US_CSR_RXRDY) 
    {
        // Lit le caractère
        usart_getchar(USART1, (uint32_t *)&read_buffer);        
    }
}

Il peut être utile de réinitialiser les buffers Tx et Rx comme ceci :

// Désactive l'interruption RXBUFF
usart_disable_interrupt(USART1, US_IDR_RXBUFF);
    
// Réinitialise TX et RX
usart_reset_rx(USART1);
usart_reset_tx(USART1);

// Active TX et RX
usart_enable_tx(USART1);
usart_enable_rx(USART1);

// Active l'interruption RXRDY
usart_enable_interrupt(USART1, US_IER_RXRDY);

Code source : tp3-usart.zip

Test :

$ make
$ ./run.sh xxx_flash.elf 

À vous de jouer :

  1. Tester la première version du programme.

  2. Configurer maintenant le port en 9600 bits/s et écrire une version utilisant les interruptions pour effectuer un echo des caractères reçus.

Travail demandé

  1. Écrire le programme de test pour les périphériques RS232 mis à votre disposition (panneaux lumineux, pupitre, etc …).

Bilan

Vous devez être capable de répondre aux questions suivantes :

  1. Quels sont les paramètres de configuration d’un port série ?

  2. Quel est le rôle du bit de START ?

  3. Quel est le rôle du bit de parité ?

  4. Quelle est la valeur du bit de parité paire pour la séquence suivante : 01100000 ?

  5. Rechercher la signification de baud.

  6. Quel est le rôle d’un contrôle de flux ?

  7. Citer les avantages et inconvénients des contrôle de flux RTS/CTS et Xon/Xoff.

  8. Calculer la durée de transmission d’un caractère sur 8 bits avec la configuration suivante : 9600 bits/s, pas de parité et 1 bit de stop. Calculer alors la durée de transmission d’une trame de 769 caractères.

  9. Comparer les normes RS232 et RS485.

  10. Citer plusieurs technologies de bus série.

Glossaire

UART
UART (Universal Asynchronous Receiver Transmitter) est le composant utilisé pour faire la liaison entre l’ordinateur et le port série. L’ordinateur envoie les données en parallèle (autant de fils que de bits de données). Il faut donc transformer ces données pour les faire passer à travers une liaison série qui utilise un seul fil pour faire passer tous les bits de données.
USART
USART est une abréviation signifiant Universal Synchronous & Asynchronous Receiver Transmitter. C’est un circuit électronique qui permet de mettre en série (sérialiser) les octets à transmettre. Cela signifie que les bits constituant l’octet sont transmis les uns après les autres. Beaucoup de circuits intégrés disposent désormais d’UART qui peuvent communiquer de manière synchrone : de tels périphériques portent le nom d’USART.
Trame UART
Une trame UART est constituée des bits suivants : un bit de start toujours à 0 (servant à la synchronisation du récepteur), les données (la taille peut varier généralement entre 5 et 9 bits), éventuellement un bit de parité paire ou impaire et un bit de stop toujours à 1 (la durée peut varier entre 1, 1,5 et 2 temps bit). Les vitesses de transmission sont normalisées par multiples et sous-multiples de 9600 baud, l’unité baud correspondant à un bit par seconde.
Parité
Le bit de PARITÉ (facultatif) est un bit généré lors de l’émission et testé lors de la réception pour détecter une erreur de transmission. La parité est paire (even) lorsque le nombre de bits (donnée + parité) est pair. La parité est impaire (odd) lorsque le nombre de bits (donnée + parité) est impair.
RS485
EIA-485 (souvent appelée RS-485) est une norme qui définit les caractéristiques électriques de la couche physique d’une interface numérique sérielle utilisée dans de nombreux réseaux industriels (Modbus, Profibus, …). Ses caractéristiques essentielles sont : liaison multi-point permettant d’interconnecter plusieurs dispositifs (jusqu’à 32 émetteurs et 32 récepteurs), bus informatique cablé avec 2 fils (en half duplex) ou 4 fils (full duplex), distance maximale de l’ordre du kilomètre en mode différentiel (qui permet d’obtenir une meilleur tolérance aux perturbations) et débit élévé jusqu’à 10Mbits/s.
CRC
Le Contrôle de Redondance Cyclique (Cyclical Redundancy Check) est utilisé dans les trames pour détecter une erreur de transmission. Il est généralement sur 16 bits ou 32 bits. Le CRC est calculé par l’émetteur avant d’être transmis. Le récepteur calcule aussi un CRC avec la trame reçue et le compare avec le CRC reçu : des valeurs différentes indiqueront une erreur dans la transmission du message. Dans les systèmes industriels, le calcul du CRC est douvent basé sur l’utilisation du OU EXCLUSIF (XOR), soit l’opérateur ^ en langage C.
Contrôle de flux
Le contrôle de flux, dans un réseau informatique, représente un asservissement du débit binaire des données transmises de l’émetteur vers le récepteur. Le stop and wait est la forme la plus simple de contrôle de flux. En communication série asynchrone RS232, deux modes de contrôle de flux sont proposés : en hardware via les lignes RTS/CTS (Ready To Send/Clear To Send) ou sous contrôle logiciel via les caractères ASCII XON/XOFF. Le contrôle hardware en RS232 nécessite 5 fils (Rx, Tx, Gnd, RTS, CTS) et le contrôle logiciel n’en nécessite que 3 (Rx, Tx, Gnd).
RTS/CTS
Les signaux RTS et CTS permettent le contrôle de flux matériel entre deux ETTD (Équipement Terminal de Traitement de Données : par exemple un ordinateur). Lorsque le buffer de réception est plein, RTS est désactivé. Il sera réactivé lorsque les données du buffer auront été lues (DTR et DSR peuvent être utilisés selon le même principe). L’émetteur doit donc scruter son CTS pour savoir s’il peut émettre ou non.
Xon/Xoff
Ce protocole basé sur le même principe que le précédent si ce n’est que Xon et Xoff sont des caractères qui valent respectivement 10 et 13 en hexadécimal. Le récepteur signale que son buffer de réception est plein en envoyant un caractère Xoff à l’émetteur. Lorsqu’il peut à nouveau accepter des caractères en réception, il envoie Xon. La liaison doit être full duplex pour que ce protocole puisse fonctionner (half duplex pour RTS/CTS).
Simplex
L’exploitation de la ligne se fait en mode unidirectionnel.
Half Duplex
La transmission est possible dans les deux sens mais pas simultanément.
Full Duplex
L’exploitation de la ligne se fait en mode bidirectionnel simultané sur le même support physique.

Retour