TD3: Gestion des interruptions

Implémentation de buffer circulaire

Explication

Le buffer circulaire est une structure de données à taille fixe doté d'un pointeur de lecture et d'un pointeur d'écriture. Ces pointeurs reviennent à zéro lorsque l'on atteint la fin du buffer. Un processus "producteur" va placer des données dans le buffer afin qu'elles soient consommées par un autre processus

Une visualisation d'un ring buffer de 16 octets. La chaîne "Ca va bien?" n'a pas encore été consommée par le processus consommateur.

Programmation

Avant d'aller plus loin, nous allons écrire le code C d'un buffer circulaire. Voici à quoi devrai ressembler le fichier d'en-têtes :

#define RING_BUFFER_SIZE 32
struct ring_buffer {
    // ...
};

// Initialise le buffer circulaire
void ring_buffer_init(struct ring_buffer *rb);
// Ajoute un octet dans le buffer circulaire
void ring_buffer_put(struct ring_buffer *rb, uint8_t data);
// Récupère un octet du buffer circulaire
uint8_t ring_buffer_get(struct ring_buffer *rb);
// Indique le nombre d'octets disponibles dans le buffer circulaire
uint8_t ring_buffer_available_bytes(struct ring_buffer *rb);
// Indique si le buffer circulaire est plein
uint8_t ring_buffer_is_full(struct ring_buffer *rb);

Testez le code du buffer circulaire en compilant sur votre ordinateur un programme de test. Pensez notamment à tester les cas limites (lecture dans un buffer vide, écriture dans un buffer plein).

Interruptions et UART: défrichage

Questions

Aidez vous de la section USART0 (de la page 143 à la page 165) de la datasheet de l'ATmega328P pour répondre aux questions ci-dessous:

  • Quelles sont les sources interruptions disponible sur l'UART ?
  • Dans quelles conditions s'activent-elles ?
  • Comment les activer ?
  • De combien de buffers circulaires aurons nous besoin ? Quand écrirons nous et lirons nous dedans ?

Implémentation

Mettez désormais à jour le code de l'UART afin d'utiliser les interruptions pour l'envoi et la réception de données.

Commencez par écrire les fonctions suivante afin de gérer l'envoi de données :

// Initialise l'UART à la vitesse donnée
void uart_init(uint32_t baudrate);
// Envoie un octet sur l'UART
void uart_send_byte(uint8_t data);
// Envoie une chaîne de caractères sur l'UART
void uart_send_string(const char *str);

Puis les fonctions suivantes afin de gérer la réception de données :

// Indique si l'UART a au moins un octet disponible
uint8_t uart_available();
// Récupère un octet de l'UART
uint8_t uart_read_byte();

Reproduisez dans un firmware uart le comportement du TD précédent avec votre nouvelle implémentation de l'UART.

$ minicom -b 115200 -o -D /dev/ttyACM0
Entrez du texte: Hello World!
Vous avez saisi: Hello World!

Taille du firmware

À l'aide de la commande:

$ avr-size --mcu=atmega328p -C build/firmware.elf

Il est possible de connaître la taille du programme généré. Comparez la taille de la section "Program" si vous utilisez des buffers circulaires de 31, 32 et 33 octets.

Comment expliquez-vous cela ?

Vous pouvez vous aider de avr-objdump -d build/firmware.elf dans ces cas pour comprendre ce qui se passe.

Correction