« I2L 2024 Groupe3 » : différence entre les versions
Ligne 661 : | Ligne 661 : | ||
[[Fichier:20250427 161536.mp4|vignette|Fonctionnement final du sonar, servo moteur, LCD|néant]] | [[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: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)|néant]] | [[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]] | [[Fichier:20250428 001715.mp4|vignette|Mode autonome désactivé (back-end)|néant]] | ||
Version du 27 avril 2025 à 23:01
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
- schéma : Fichier:I2L-2024-Carte-G3a.zip
Carte routée
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
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
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
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é :
- 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
rotateServo()
. - 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.
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
Rendus
Projet KiCAD : Fichier:I2L-2024-Carte-G3.zip
Programmes :
- démonstration : Fichier:I2L-2024-Programmes-G3-rex.zip
- SONAR : Fichier:I2L-2024-Programmes-G3.zip