« I2L 2024 Groupe3 » : différence entre les versions

De wiki-se.plil.fr
Aller à la navigation Aller à la recherche
(28 versions intermédiaires par 3 utilisateurs non affichées)
Ligne 199 : Ligne 199 :
     }
     }
}
}
uint32_t convertAngleIntoDegree(uint32_t angle) {
    return (angle - ANGLE_MIN) * (180.0f / (ANGLE_MAX - ANGLE_MIN));
}
</syntaxhighlight>
</syntaxhighlight>


Ligne 207 : Ligne 212 :
#include "led.h"
#include "led.h"
#include "timer.h"
#include "timer.h"
float lastTimerTime = 0;
 
float currentTimerTime = 0;
float autonomousLedLastTimerTime = 0;
bool isLightOn = false;
float autonomousLedcurrentTimerTime = 0;
bool isAutonomousLightOn = false;
bool autonomousLedIsActive = false;
 
float objectDetectedLedLastTimerTime = 0;
float objectDetectedLedCurrentTimerTime = 0;
bool isObjectDetectedLightOn = false;
bool objectDetectedLedIsActive = false;
int objectDetectedLedBlinkCounter = 0;
 


void setupLed(void) {
void setupLed(void) {
     // Configurer les broches LED1 et LED2 comme sorties
     // Configurer les broches LED1 et LED2 comme sorties
     DDRE |= (1 << LED1);
     DDRE |= (1 << LED1);
Ligne 218 : Ligne 231 :
}
}


void turnOnAutonomousLed(void) {  
void handleAutonomousLed() {
     currentTimerTime = currentTimer;
     if (autonomousLedIsActive) {
    if (isLightOn == true) {
        autonomousLedCurrentTimerTime = currentTimer;
        if (currentTimerTime - lastTimerTime >= AUTONOMOUS_LIGHT_ON_TIME) {
        if (isAutonomousLightOn == true) {
            lastTimerTime = currentTimerTime;
            if (autonomousLedCurrentTimerTime - autonomousLedLastTimerTime >= AUTONOMOUS_LIGHT_ON_TIME) {
            isLightOn = false;
                autonomousLedLastTimerTime = autonomousLedCurrentTimerTime;
            PORTE &= ~(1 << LED1); // light off
                isAutonomousLightOn = false;
                PORTE &= ~(1 << LED1); // light off
            }
        }
        else {
            if (autonomousLedCurrentTimerTime - autonomousLedLastTimerTime >= AUTONOMOUS_LIGHT_OFF_TIME) {
                autonomousLedLastTimerTime = autonomousLedCurrentTimerTime;
                isAutonomousLightOn = true;
                PORTE |= (1 << LED1); // light on
            }
         }
         }
     }
     }
     else {
     else {
         if (currentTimerTime - lastTimerTime >= AUTONOMOUS_LIGHT_OFF_TIME) {
         PORTE &= ~(1 << LED1);
            lastTimerTime = currentTimerTime;
         _delay_ms(10);
            isLightOn = true;
            PORTE |= (1 << LED1); // light on
         }
     }
     }
}
void turnOnAutonomousLed(void) {   
    autonomousLedIsActive = true;
}
}


void turnOffAutonomousLed(void) {
void turnOffAutonomousLed(void) {
     PORTE &= ~(1 << LED1);
     autonomousLedIsActive = false;
}
}


void blinkObjectDetectedLed(void) {
void handleObjectDetectedLed() {
     PORTF |= (1 << LED2);
     if(objectDetectedLedIsActive && objectDetectedLedBlinkCounter < 2) {
    _delay_ms(100);
        objectDetectedLedCurrentTimerTime = currentTimer;
    PORTF &= ~(1 << LED2);
        if (isObjectDetectedLightOn == true) {
     _delay_ms(300);
            if (objectDetectedLedCurrentTimerTime - objectDetectedLedLastTimerTime >= OBJECT_DETECTED_LIGHT_ON_TIME) {
                objectDetectedLedLastTimerTime = objectDetectedLedCurrentTimerTime;
                isObjectDetectedLightOn = false;
                PORTE &= ~(1 << LED2); // light off
                objectDetectedLedBlinkCounter++;
            }
        }
        else {
            if (objectDetectedLedCurrentTimerTime - objectDetectedLedLastTimerTime >= OBJECT_DETECTED_LIGHT_OFF_TIME) {
                objectDetectedLedLastTimerTime = objectDetectedLedCurrentTimerTime;
                isObjectDetectedLightOn = true;
                PORTE |= (1 << LED2); // light on
               
            }
        }
    }
     else {
        objectDetectedLedIsActive = false;
        objectDetectedLedBlinkCounter=0;
    }
}


    PORTF |= (1 << LED2);
void turnOnObjectDetectedLed(void) {
     _delay_ms(100);
     objectDetectedLedIsActive = true;
    PORTF &= ~(1 << LED2);
}
     _delay_ms(300);
void turnOffObjectDetectedLed(void) {
     objectDetectedLedIsActive = false;
}
}
</syntaxhighlight>
</syntaxhighlight>
Ligne 346 : Ligne 390 :
     if (col >= nbcols) col = nbcols - 1;
     if (col >= nbcols) col = nbcols - 1;
     return row_offsets[row] + col;
     return row_offsets[row] + col;
}
</syntaxhighlight>
=== '''firmware/VirtualSerial.c''' ===
La fonction <code>CDC_Task</code> gère les communications USB en mode CDC, exécutant plusieurs actions lorsque le périphérique est configuré :
# Elle gère tout d'abord l'input utilisateur sur les boutons, activant ou désactivant le mode autonome du micro controller et allumant ou éteignant les LEDs en conséquences
# Elle fait tourner le servo-moteur avec <code>rotateServo()</code>.
# Elle mesure la distance via le capteur sonar et récupère l'angle du servo-moteur.
# Elle formate les données (distance et angle) dans une chaîne de caractères.
# Elle met à jour l'affichage LCD avec ces informations.
# Elle met à jour les LED, en les allumant ou éteignant en fonction de leurs timers respectifs.
# Si le périphérique USB est prêt, elle envoie les données via USB.
Elle coordonne donc les inputs utilisateur sur les boutons, les LEDs, le mouvement du servo, les mesures, l'affichage et la communication USB.<syntaxhighlight lang="c">
void CDC_Task(void) {
    if (USB_DeviceState != DEVICE_STATE_Configured) return;
    Endpoint_SelectEndpoint(CDC_TX_EPADDR);
    if (checkButtonRelease()) {
        autonomousMode = !autonomousMode;
    }
    if(autonomousMode) {
        turnOnAutonomousLed();
    }
    else {
        turnOffAutonomousLed();
    }
   
    // servo
    rotateServo();
   
    char buffer[64];
    uint32_t distance = sonarMesure();
    uint32_t angle = convertAngleIntoDegree(getAngle());
    long int distanceCM = (long int)(distance * 1000000);
    // LCD
    handleScreen(distance, angle);
    // LED
    handleAutonomousLed();
    handleObjectDetectedLed();
    if(autonomousMode == false){
        snprintf(buffer, sizeof(buffer), "%ld.%06ld,%02lu\n",
        distanceCM / 1000000, distanceCM % 1000000, angle);
    }
    if (Endpoint_IsINReady()) {
        Endpoint_Write_Stream_LE(buffer, strlen(buffer), NULL);
        Endpoint_ClearIN();
    }
}
}
</syntaxhighlight>
</syntaxhighlight>
Ligne 352 : Ligne 449 :
Exemple de donnée envoyée sur le port /dev/ttyACM0 via la communication serial.
Exemple de donnée envoyée sur le port /dev/ttyACM0 via la communication serial.


(mesure du sonar, angle du servomoteur)<syntaxhighlight lang="text">
(mesure du sonar en millisecondes, angle du servomoteur)<syntaxhighlight lang="text">
8.000000,96
8.000000,96
8.000000,96
8.000000,96
Ligne 394 : Ligne 491 :


===pc_app/'''SonarFront.py'''===
===pc_app/'''SonarFront.py'''===
Le code lit des coordonnées (rayon, angle) depuis un périphérique série ou virtuel et les affiche sur un graphique polaire. Les points sont mis à jour en temps réel avec un intervalle de 10 ms. En mode virtuel, un processus simule le périphérique série.<syntaxhighlight lang="python">
Le code lit des coordonnées (rayon, angle) depuis un périphérique série ou virtuel et les affiche sur un graphique polaire. Les points sont mis à jour en temps réel avec un intervalle de 10 ms. En mode virtuel, un processus simule le périphérique série.
 
La variable <code>VIRTUAL_DEVICE_MODE</code> détermine si le programme utilise un périphérique réel (<code>False</code>) ou un périphérique virtuel (<code>True</code>) pour la lecture des données. Lorsque <code>VIRTUAL_DEVICE_MODE</code> est définie sur <code>True</code>, le programme crée un fichier FIFO virtuel (un tube) et lance un script de simulation pour générer des données fictives. Si elle est définie sur <code>False</code>, le programme lit les données directement depuis un port série réel (<code>/dev/ttyUSB0</code>).<syntaxhighlight lang="python">
import serial
import serial
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt
Ligne 560 : Ligne 659 :
= Démonstrations =
= Démonstrations =
[[Fichier:VID 20250424 222954.mp4|gauche|Présentation du fonctionnement du sonar, ainsi que le rôle du moteur et de l’écran LCD. (V1)|vignette|Présentation du fonctionnement du sonar, ainsi que le rôle du moteur et de l’écran LCD. (V1)]]
[[Fichier:VID 20250424 222954.mp4|gauche|Présentation du fonctionnement du sonar, ainsi que le rôle du moteur et de l’écran LCD. (V1)|vignette|Présentation du fonctionnement du sonar, ainsi que le rôle du moteur et de l’écran LCD. (V1)]]
[[Fichier:20250427 161536.mp4|centré|vignette|Fonctionnement final du sonar, servo moteur, LCD]]
[[Fichier:20250427 161536.mp4|vignette|Fonctionnement final du sonar, servo moteur, LCD|néant]]
 
[[Fichier:20250427 235016.mp4|gauche|vignette|mode autonome activé ((back-end) LED verte remplacé par LED rouge car inutilisable)]]
[[Fichier:20250428 002154.mp4|vignette|VIRTUAL DEVICE MODE activé (front-end, génération de données aléatoires)|néant]]
[[Fichier:20250428 001715.mp4|vignette|Mode autonome désactivé (back-end)|néant]]


= Rendus =
= Rendus =
Ligne 568 : Ligne 669 :


Programmes :  
Programmes :  
* démonstration : [[File:I2L-2024-Programmes-G3-rex.zip]]
* SONAR : [[File:I2L-2024-Programmes-G3.zip]]
* SONAR : [[File:I2L-2024-Programmes-G3.zip]]

Version du 27 avril 2025 à 23:24

Proposition de système

Création d'un sonar 180 degrés qui écrit des données rudimentaires sur un écran LCD et qui, lorsqu'on connecte le microcontrôleur au PC, envoi en temps réel les données au PC et génère un affichage style sonar.

On a 2 programmes :

  • 1 programme qui tourne dans le microcontrôleur, il est autonome, fait tourner a l'aide d'un servo-moteur le sonar (capteur style sonar) et affiche des informationss rudimentaires sur les obstacles repérés (e.g. l'obstacle le plus proche) ;
  • 1 programme qui tourne sur le PC connecté au microcontrôleur. Il récupère les données en temps réel envoyées par le microcontrôleur et affiche graphiquement le résultat ;
  • 2 LED de couleur (vert et rouge), la LED verte clignote toute les 3 secondes quand le système embarqué est autonome et la LED rouge, éteinte par défaut, double clignote quand un obstacle est détecté.

A prévoir un bouton "autonomous_system" sur le microcontroleur, qui, lorsque celui-ci est branché au PC, n'envoie pas les données au PC.

Contre-proposition

OK pour la proposition.

Pour la gestion de l'écran vous pouvez vous inspirer du code des groupes 3 et 4 I2L en 2023/2024.

Vous utiliserez la classe USB "vendeur spécifique" avec des points d'accès propres à votre application. Plus exactement vous prévoierez un point d'accès sortant pour spécifier l'angle du servo-moteur et un point d'accès entrant pour récupérer la distance mesurée.

Pour l'application sur PC vous utiliserez la bibliothèque C libusb-1.0.

Il faut aussi penser à un système pour assembler le servo-moteur et le sonar. Si vous avez accès à une imprimante 3D suivez ce lien [1].

Proposition définitive

Le projet consiste en la création d’un sonar à balayage 180° capable de détecter des obstacles et d’afficher les données recueillies de deux façons complémentaires :

  • Un premier programme est embarqué dans le microcontrôleur. Il pilote un capteur à ultrasons monté sur un servomoteur afin de balayer un angle de 180°. Ce programme fonctionne de manière autonome et affiche des informations rudimentaires sur un écran LCD, comme la distance de l'obstacle le plus proche détecté.
  • Un second programme, exécuté sur un ordinateur connecté au microcontrôleur, reçoit en temps réel les données envoyées par celui-ci via USB. Il se charge de les représenter graphiquement sous forme d’un affichage de type sonar 180°, facilitant la visualisation des obstacles détectés dans l’environnement.

Répartition du travail

La répartition des tâches au sein du groupe s’est organisée de la manière suivante :

  • Thomas a pris en charge la gestion de l’écran LCD ainsi que le contrôle du servomoteur.
  • Adrien s’est occupé de l’architecture générale du code, du pilotage du sonar, et du Frontend python.
  • Matthieu a travaillé sur l’intégration de LUFA (USB), une tâche complexe ayant nécessité plusieurs ajustements.

Par ailleurs, de nombreuses sessions de pair programming ont été menées, notamment lors de l’apparition de problèmes liés à la LUFA.

Carte

Schéma initial

Carte routée

Schéma de la carte
vue de la carte

Composants

  • ATmega32u4 : disponible
  • quartz GND24 : disponible
  • buzzer : disponible
  • perle ferrite MH2029-300Y : commandée
  • chargeur MAX1811 : disponible
  • potentiomètre : disponible
  • connecteur femelle 16 contacts : commandé
  • écran LCD 2 lignes : commandé
  • boutons : disponibles
  • sonar HC-SR4 : disponible
  • servo-moteur 270° : disponible

Carte au 23/02/2025

Première réalisation

Non encore réalisé :

  • ajouter la perle de ferrite ;
  • ajouter les connecteurs J5, J6, J7 et J9 pour la charge ;
  • ajouter le condensateur de 2,2uF pour la charge ;
  • ajouter R8 et le potentiomètre pour l'écran LCD ;
  • ajouter le buzzer ;
  • ajouter M1 et J3 pour le servo-moteur et le sonar.
  • remplacer une led rouge par une led verte.

Carte au 27/02/2025

Poursuite de la réalisation

Non encore réalisé :

  • ajouter la perle de ferrite ;
  • remplacer une led rouge par une led verte.

Carte au 07/03/2025

Une erreur a été introduite au moment du routage de la carte : la ligne de commande du serveur a été mise sur PF6 qui ne peut pas être commandée en PWM par un compteur. Du coup, des fils sont soudés à la place du buzzer pour commander le servo avec PB7.

Montage

Montage final avec affichage front sur le PC

Code Backend

firmware/sonar.c

Le capteur HC-SR04 émet un signal ultrasonore via la broche TRIGGER, puis mesure le temps nécessaire à ce signal pour rebondir sur un objet et revenir à la broche ECHO. La distance est ensuite calculée en fonction du temps écoulé, en utilisant la vitesse du son, et la distance mesurée est retournée par la fonction sonarMesure.

//Gestion du capteur HC-SR04

#include "sonar.h"
#include <stdint.h>

bool isWaitingForSignal = false;
uint32_t startTimerWaitingForSignal = 0;
uint32_t lastDistanceMesured = 0;
SonarState sonarState = SONAR_IDLE;

void setupSonar(void) {
  US_DDR |= (1 << US_TRIGGER);   // Set TRIGGER pin as output
  US_DDR &= ~(1 << US_ECHO);     // Set ECHO pin as input
  US_PORT &= ~(1 << US_TRIGGER); // Make sure TRIGGER is LOW at startup
  US_PORT |= (1 << US_ECHO);
}

uint32_t sonarMesure(void) {

    switch (sonarState) {
        case SONAR_IDLE:
          US_PORT |= (1 << US_TRIGGER);
          _delay_us(10);
          US_PORT &= ~(1 << US_TRIGGER);
          sonarState = SONAR_WAIT_RISING;
          break;

        case SONAR_WAIT_RISING:
            startTimerWaitingForSignal = currentTimer;
            sonarState = SONAR_WAIT_FALLING;
            break;

        case SONAR_WAIT_FALLING:
          if (!(US_PIN & (1 << US_ECHO))) {
            uint32_t waitingTimeForSignal = currentTimer - startTimerWaitingForSignal;
            lastDistanceMesured = waitingTimeForSignal * SOUND_SPEED; 
            sonarState = SONAR_IDLE;
          }
          break;
  }
  return lastDistanceMesured;
}

firmware/servo.c

Le code contrôle un servo-moteur à l'aide d'un timer et d'un signal PWM (modulation de largeur d'impulsion). Le signal est généré sur la broche OC1A (PF6), et le mouvement du servo est ajusté en fonction d'un angle défini. La fonction setServoAngle ajuste l'angle du servo, tandis que rotateServo fait tourner progressivement le moteur entre des limites définies (ANGLE_MIN et ANGLE_MAX), en inversant la direction une fois l'angle maximal ou minimal atteint. La fréquence de mise à jour est de 36 ms

//# Contrôle du servo-moteur
#include "servo.h"
#include <util/delay.h>

// Initialisation du servo sur OC1A (PF6)
void setupServo() {
    /* Timer 0 control pins PB7 and PD0 */
    DDRB |= 0x80; // Pin PB7
    /* Timer 2 configuration               */
    /*   - WGM0 for fast PWM = 011         */
    /*   - COM0A for normal outputs = 10   */
    /*   - COM0B desactivate PD0 = 00      */
    /*   - CS0 with 256 as prescaler = 100 */
    TCCR0A |= (1<<COM0A1) | (1<<WGM01) | (1<<WGM00);
    TCCR0B |= (1<<CS02);
    /* So one unit in OCR0A represents 256/16000000*1000*1000=16 us      */
    /* For a pulse of 1ms, 62 units are needed, 125 for a pulse of 2ms   */
    /* The servo-motor must work with a period of 4ms                    */
    servoDirection = 1;
}

void setAngle(uint32_t newAngle){
    angle = newAngle;
}

// Définir l'angle du servo
void setServoAngle(int angle) {
    OCR0A=angle;
    setAngle(angle);
}

uint32_t getAngle(void){
    return angle;
}

int getServoDirection(void){
    return servoDirection;
}

void setServoDirection(int newDirection){
    servoDirection = newDirection;
}

// Faire tourner le servo progressivement
void rotateServo() {
    static uint32_t lastUpdateTime = 0;
    // Vérifier si assez de temps est passé
    if (currentTimer - lastUpdateTime >= 36) { // toutes les 36ms
        uint32_t currentAngle = getAngle();
        if (currentAngle >= ANGLE_MAX) {
            setServoDirection(-1);
        } else if (currentAngle <= ANGLE_MIN) {
            setServoDirection(1);
        }
        setServoAngle(currentAngle + getServoDirection()); // Avancer de 1 ou reculer de 1
        lastUpdateTime = currentTimer; // Mettre à jour le dernier temps
    }
}

uint32_t convertAngleIntoDegree(uint32_t angle) {
    return (angle - ANGLE_MIN) * (180.0f / (ANGLE_MAX - ANGLE_MIN));
}

firmware/led.c

Le code gère deux LED : LED1 et LED2. LED1 alterne entre allumé et éteint à des intervalles définis, tandis que LED2 clignote pour signaler la détection d’un objet. Les broches des LED sont configurées comme sorties.

// Gestion des LED

#include "led.h"
#include "timer.h"

float autonomousLedLastTimerTime = 0;
float autonomousLedcurrentTimerTime = 0;
bool isAutonomousLightOn = false;
bool autonomousLedIsActive = false;

float objectDetectedLedLastTimerTime = 0;
float objectDetectedLedCurrentTimerTime = 0;
bool isObjectDetectedLightOn = false;
bool objectDetectedLedIsActive = false;
int objectDetectedLedBlinkCounter = 0;


void setupLed(void) {
    // Configurer les broches LED1 et LED2 comme sorties
    DDRE |= (1 << LED1);
    DDRF |= (1 << LED2);
}

void handleAutonomousLed() {
    if (autonomousLedIsActive) {
        autonomousLedCurrentTimerTime = currentTimer;
        if (isAutonomousLightOn == true) {
            if (autonomousLedCurrentTimerTime - autonomousLedLastTimerTime >= AUTONOMOUS_LIGHT_ON_TIME) {
                autonomousLedLastTimerTime = autonomousLedCurrentTimerTime;
                isAutonomousLightOn = false;
                PORTE &= ~(1 << LED1); // light off
            }
        }
        else {
            if (autonomousLedCurrentTimerTime - autonomousLedLastTimerTime >= AUTONOMOUS_LIGHT_OFF_TIME) {
                autonomousLedLastTimerTime = autonomousLedCurrentTimerTime;
                isAutonomousLightOn = true;
                PORTE |= (1 << LED1); // light on
            }
        }
    }
    else {
        PORTE &= ~(1 << LED1);
        _delay_ms(10);
    }
}

void turnOnAutonomousLed(void) {    
    autonomousLedIsActive = true;
}

void turnOffAutonomousLed(void) {
    autonomousLedIsActive = false;
}

void handleObjectDetectedLed() {
    if(objectDetectedLedIsActive && objectDetectedLedBlinkCounter < 2) {
        objectDetectedLedCurrentTimerTime = currentTimer;
        if (isObjectDetectedLightOn == true) {
            if (objectDetectedLedCurrentTimerTime - objectDetectedLedLastTimerTime >= OBJECT_DETECTED_LIGHT_ON_TIME) {
                objectDetectedLedLastTimerTime = objectDetectedLedCurrentTimerTime;
                isObjectDetectedLightOn = false;
                PORTE &= ~(1 << LED2); // light off
                objectDetectedLedBlinkCounter++;
            }
        }
        else {
            if (objectDetectedLedCurrentTimerTime - objectDetectedLedLastTimerTime >= OBJECT_DETECTED_LIGHT_OFF_TIME) {
                objectDetectedLedLastTimerTime = objectDetectedLedCurrentTimerTime;
                isObjectDetectedLightOn = true;
                PORTE |= (1 << LED2); // light on
                
            }
        }
    }
    else {
        objectDetectedLedIsActive = false;
        objectDetectedLedBlinkCounter=0;
    }
}

void turnOnObjectDetectedLed(void) {
    objectDetectedLedIsActive = true;
}
void turnOffObjectDetectedLed(void) {
    objectDetectedLedIsActive = false;
}

firmware/HD44780.c

Communication avec un écran LCD HD44780 en mode 4 bits. Initialise l'écran, envoie des commandes, écrit du texte ou affiche des nombres correspondant a la distance d'un obstacle et son angle.

#include <stdlib.h>
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>
#include <string.h>
#include "HD44780_private.h"
#include "HD44780.h"

static void HD44780_WriteNibble(const uint8_t nib){
    LCD_EN_PORT &= ~(1<<LCD_EN_PIN);

    if(nib & 0x08) LCD_D7_PORT |= (1<<LCD_D7_PIN);
    else LCD_D7_PORT &= ~(1<<LCD_D7_PIN);
    if(nib & 0x04) LCD_D6_PORT |= (1<<LCD_D6_PIN);
    else LCD_D6_PORT &= ~(1<<LCD_D6_PIN);
    if(nib & 0x02) LCD_D5_PORT |= (1<<LCD_D5_PIN);
    else LCD_D5_PORT &= ~(1<<LCD_D5_PIN);
    if(nib & 0x01) LCD_D4_PORT |= (1<<LCD_D4_PIN);
    else LCD_D4_PORT &= ~(1<<LCD_D4_PIN);
    _delay_us(1);
    LCD_EN_PORT |= (1<<LCD_EN_PIN);
    _delay_us(1);
    LCD_EN_PORT &= ~(1<<LCD_EN_PIN);
    _delay_us(100);
}

static void HD44780_WriteByte(const uint8_t c){
    HD44780_WriteNibble(c >> 4);
    HD44780_WriteNibble(c & 0x0F);
}

static void HD44780_PowerUp4Bit(void){
    _delay_ms(40);
    HD44780_WriteNibble(0x03);
    _delay_ms(5);
    HD44780_WriteNibble(0x03);
    _delay_us(100);
    HD44780_WriteNibble(0x03);
    _delay_us(50);
    HD44780_WriteNibble(0x02);
    _delay_us(50);
}

void HD44780_Initialize(void){
    LCD_D4_DDR |= (1<<LCD_D4_PIN);
    LCD_D5_DDR |= (1<<LCD_D5_PIN);
    LCD_D6_DDR |= (1<<LCD_D6_PIN);
    LCD_D7_DDR |= (1<<LCD_D7_PIN);
    LCD_RS_DDR |= (1<<LCD_RS_PIN);
    LCD_RW_DDR |= (1<<LCD_RW_PIN);
    LCD_EN_DDR |= (1<<LCD_EN_PIN);
    LCD_D4_PORT &= ~(1<<LCD_D4_PIN);
    LCD_D5_PORT &= ~(1<<LCD_D5_PIN);
    LCD_D6_PORT &= ~(1<<LCD_D6_PIN);
    LCD_D7_PORT &= ~(1<<LCD_D7_PIN);
    LCD_RS_PORT &= ~(1<<LCD_RS_PIN);
    LCD_RW_PORT &= ~(1<<LCD_RW_PIN);
    LCD_EN_PORT &= ~(1<<LCD_EN_PIN);
    HD44780_PowerUp4Bit();
    _delay_ms(50);
}

void HD44780_WriteCommand(const uint8_t c){
    LCD_RS_PORT &= ~(1<<LCD_RS_PIN);
    HD44780_WriteByte(c);
    _delay_us(50);
}

void HD44780_WriteData(const uint8_t c){
    LCD_RS_PORT |= (1<<LCD_RS_PIN);
    HD44780_WriteByte(c);
    LCD_RS_PORT &= ~(1<<LCD_RS_PIN);
    _delay_us(50);
}

void HD44780_WriteString(char *string){
    for (int i = 0; i < strlen(string); i++)
    HD44780_WriteData(string[i]);
}

void HD44780_WriteInteger(int num, int radix){
    char s[MAX_DIGITS];
    itoa(num, s, radix);
    HD44780_WriteString(s);
}

int HD44780_XY2Adrr(int nbrows, int nbcols, int row, int col){
    int row_offsets[] = {0x00, 0x40, 0x14, 0x54};
    if (row >= nbrows) row = nbrows - 1;
    if (col >= nbcols) col = nbcols - 1;
    return row_offsets[row] + col;
}

firmware/VirtualSerial.c

La fonction CDC_Task gère les communications USB en mode CDC, exécutant plusieurs actions lorsque le périphérique est configuré :

  1. Elle gère tout d'abord l'input utilisateur sur les boutons, activant ou désactivant le mode autonome du micro controller et allumant ou éteignant les LEDs en conséquences
  2. Elle fait tourner le servo-moteur avec rotateServo().
  3. Elle mesure la distance via le capteur sonar et récupère l'angle du servo-moteur.
  4. Elle formate les données (distance et angle) dans une chaîne de caractères.
  5. Elle met à jour l'affichage LCD avec ces informations.
  6. Elle met à jour les LED, en les allumant ou éteignant en fonction de leurs timers respectifs.
  7. Si le périphérique USB est prêt, elle envoie les données via USB.

Elle coordonne donc les inputs utilisateur sur les boutons, les LEDs, le mouvement du servo, les mesures, l'affichage et la communication USB.

void CDC_Task(void) {
    if (USB_DeviceState != DEVICE_STATE_Configured) return;
    Endpoint_SelectEndpoint(CDC_TX_EPADDR);

    if (checkButtonRelease()) {
        autonomousMode = !autonomousMode;
    }

    if(autonomousMode) {
        turnOnAutonomousLed();
    }
    else {
        turnOffAutonomousLed();
    }
    
    // servo
    rotateServo();
    
    char buffer[64];
    uint32_t distance = sonarMesure();
    uint32_t angle = convertAngleIntoDegree(getAngle());
    long int distanceCM = (long int)(distance * 1000000);

    // LCD
    handleScreen(distance, angle);

    // LED
    handleAutonomousLed();
    handleObjectDetectedLed();

    if(autonomousMode == false){
        snprintf(buffer, sizeof(buffer), "%ld.%06ld,%02lu\n", 
        distanceCM / 1000000, distanceCM % 1000000, angle);
    }
    if (Endpoint_IsINReady()) {
        Endpoint_Write_Stream_LE(buffer, strlen(buffer), NULL);
        Endpoint_ClearIN();
    }
}

Data

Exemple de donnée envoyée sur le port /dev/ttyACM0 via la communication serial.

(mesure du sonar en millisecondes, angle du servomoteur)

8.000000,96
8.000000,96
8.000000,96
8.000000,97
8.000000,97
8.000000,97
8.000000,97
8.000000,97
8.000000,98
8.000000,98
8.000000,98
8.000000,98
8.000000,98
8.000000,99
8.000000,99
8.000000,99
8.000000,99
9.000000,99
9.000000,100
9.000000,100
8.000000,100
8.000000,100
8.000000,100
8.000000,101
8.000000,101
8.000000,101
9.000000,101
9.000000,101
9.000000,102
9.000000,102
9.000000,102
9.000000,102
9.000000,102
9.000000,103

Code Front

pc_app/SonarFront.py

Le code lit des coordonnées (rayon, angle) depuis un périphérique série ou virtuel et les affiche sur un graphique polaire. Les points sont mis à jour en temps réel avec un intervalle de 10 ms. En mode virtuel, un processus simule le périphérique série.

La variable VIRTUAL_DEVICE_MODE détermine si le programme utilise un périphérique réel (False) ou un périphérique virtuel (True) pour la lecture des données. Lorsque VIRTUAL_DEVICE_MODE est définie sur True, le programme crée un fichier FIFO virtuel (un tube) et lance un script de simulation pour générer des données fictives. Si elle est définie sur False, le programme lit les données directement depuis un port série réel (/dev/ttyUSB0).

import serial
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
import subprocess
import os
import select

# Mode d'utilisation d'un périphérique virtuel (True) ou réel (False)
VIRTUAL_DEVICE_MODE = False

# Initialisation du périphérique virtuel si nécessaire
if VIRTUAL_DEVICE_MODE:
    VIRTUAL_PORT = '/tmp/virtual_serial'
    if not os.path.exists(VIRTUAL_PORT):
        os.mkfifo(VIRTUAL_PORT)
    # Lance le script qui simule un périphérique série
    sim_process = subprocess.Popen(['/bin/bash', './pc_app/simulate_serial.sh'])

def read_coordonnees():
    """
    Lit une ligne de données depuis le port série ou virtuel,
    la décode, puis retourne un tuple (rayon, angle) en float.
    Retourne (0.0, 0.0) en cas d'erreur ou d'absence de données.
    """
    try:
        if VIRTUAL_DEVICE_MODE:
            with open(VIRTUAL_PORT, 'r') as f:
                rlist, _, _ = select.select([f], [], [], 0.1)
                if rlist:
                    line = f.readline().strip()
                else:
                    return (0.0, 0.0)
        else:
            with serial.Serial('/dev/ttyUSB0', 9600, timeout=1) as ser:
                line = ser.readline().decode('utf-8').strip()
        
        if line:
            parts = line.split(',')
            if len(parts) == 2:
                return (float(parts[0]), float(parts[1]))
        return (0.0, 0.0)
    
    except Exception as e:
        print(f"Erreur de lecture: {str(e)}")
        return (0.0, 0.0)

# Configuration de la fenêtre graphique
fig = plt.figure(figsize=(14, 7))
ax = fig.add_subplot(111, projection='polar')  # Graphique en coordonnées polaires
ax.set_ylim(0, 5.5)
ax.set_thetamin(0)
ax.set_thetamax(180)
ax.set_theta_offset(np.pi)  # Décalage de 180°
ax.set_theta_direction(-1)  # Sens horaire
scatter = ax.scatter([], [], color='#FF9F1C', s=120, edgecolor='#EB5E28')

# Dictionnaire pour stocker les points (clé = angle, valeur = rayon)
points_dict = {}

def update(frame):
    """
    Fonction appelée périodiquement par FuncAnimation.
    Elle lit de nouvelles coordonnées, met à jour les points existants,
    et rafraîchit l'affichage du graphique.
    """
    global points_dict
    r, theta = read_coordonnees()
    
    # Ignorer les coordonnées (0,0) (pas de donnée)
    if abs(r) < 1e-3 and abs(theta) < 1e-3:
        pass
    else:
        # Ajouter ou mettre à jour un point
        if theta in points_dict:
            if abs(r) < 1e-3:
                del points_dict[theta]  # Supprimer le point si le rayon est nul
            else:
                points_dict[theta] = r  # Mettre à jour le rayon existant
        else:
            if abs(r) >= 1e-3:
                points_dict[theta] = r  # Ajouter un nouveau point
    
    # Préparer les données pour le graphique
    data = []
    for angle_deg, radius in points_dict.items():
        angle_rad = np.deg2rad(angle_deg)
        data.append([angle_rad, radius])
    
    # Mise à jour de l'affichage
    if data:
        scatter.set_offsets(np.array(data))
    else:
        scatter.set_offsets(np.empty((0, 2)))
    
    return scatter,

# Lancement de l'animation avec mise à jour toutes les 10 ms
ani = FuncAnimation(fig, update, interval=10, blit=True)

try:
    plt.show()
finally:
    # Nettoyage du processus de simulation et suppression du tube
    if VIRTUAL_DEVICE_MODE:
        sim_process.terminate()
        try:
            os.remove(VIRTUAL_PORT)
        except:
            pass

simulate_serial.sh

Le script bash simule un balayage angulaire avec des distances aléatoires entre 0,50 m et 5,50 m. Il envoie les données sous forme "distance,angle" dans un tube nommé. L'angle est incrémenté ou décrémenté entre 0° et 180°, et la direction s'inverse lorsque l'angle atteint ces limites. Le script envoie une nouvelle donnée toutes les 10 ms.

#!/bin/bash
# simulate_serial.sh - Simule un balayage angulaire avec distance aléatoire

angle=0.0            # Angle de départ
direction=1          # Direction d'incrémentation de l'angle
pipe="/tmp/virtual_serial"  # Chemin du tube nommé pour la communication

# Forcer l'utilisation du point comme séparateur décimal
export LC_NUMERIC=C

# Créer le tube nommé si besoin
[ ! -p "$pipe" ] && mkfifo "$pipe"

# Boucle infinie de génération de données
while true; do
    # Générer une distance aléatoire entre 0.50 m et 5.50 m
    distance=$(echo "scale=2; $RANDOM/32767*5+0.5" | bc -l)
    
    # Envoyer les données sous forme "distance,angle" dans le tube
    printf "%s,%.2f\n" $distance $angle > "$pipe"
    
    # Incrémenter ou décrémenter l'angle
    angle=$(echo "scale=2; $angle + $direction" | bc -l)
    
    # Inverser la direction à 0° et 180°
    if (( $(echo "$angle >= 180.0" | bc -l) )); then
        direction="-1"
    elif (( $(echo "$angle <= 0.0" | bc -l) )); then
        direction="1"
    fi
    
    # Attendre 10 ms avant d'envoyer la prochaine donnée
    sleep 0.01
done

Lancement

Pour exécuter le programme sur la carte, il faut tout d’abord le compiler et le téléverser. Voici les étapes à suivre dans le terminal :


make clean   # Nettoie les fichiers de compilation précédents

make         # Compile le programme

reset        # Réinitialise la carte (via bouton physique sur la carte)

make upload  # Téléverse le programme sur la carte


Une fois ces commandes exécutées, la carte est prête à faire fonctionner le programme.

Démonstrations

Présentation du fonctionnement du sonar, ainsi que le rôle du moteur et de l’écran LCD. (V1)
Fonctionnement final du sonar, servo moteur, LCD
mode autonome activé ((back-end) LED verte remplacé par LED rouge car inutilisable)
VIRTUAL DEVICE MODE activé (front-end, génération de données aléatoires)
Mode autonome désactivé (back-end)

Rendus

Projet KiCAD : Fichier:I2L-2024-Carte-G3.zip

Programmes :