« I2L 2024 Groupe4 » : différence entre les versions
(→Code) |
(→Code) |
||
Ligne 71 : | Ligne 71 : | ||
=== Carte au 27/02/2025 === | === Carte au 27/02/2025 === | ||
[[File:ISL-2024-G4-Realisation-B.jpg|thumb|center|400px|Réalisation terminée]]Carte finalisée avec l'écran LCD et prête à l'emploi. | [[File:ISL-2024-G4-Realisation-B.jpg|thumb|center|400px|Réalisation terminée]]Carte finalisée avec l'écran LCD et prête à l'emploi. | ||
= | = Explication du fonctionnement du code morse = | ||
Pour comprendre la suite, il va nous falloir détailler comment fonctionne le morse, car la mesure du temps est primordiale pour comprendre comment fonctionnent la traduction et le fonctionnement. | |||
Chaque lettre en morse correspond à une suite de points et de traits bien défini. Par exemple, le signe SOS s'écrirait <code>... --- ...</code> en morse, puisque le S est composé de 3 points et le O de trois traits, comme inscrit dans le [https://fr.wikipedia.org/wiki/Code_Morse_international code international du morse]. | |||
Un point représente une unité de temps et un trait représente trois unités de temps. Chaque point ou trait est séparé d'une unité de temps. Chaque lettre est séparé de trois unités de temps soit l'équivalent d'un trait, et un mot est séparé d'un autre par sept unités de temps. | Un point représente une unité de temps et un trait représente trois unités de temps. Chaque point ou trait est séparé d'une unité de temps. Chaque lettre est séparé de trois unités de temps soit l'équivalent d'un trait, et un mot est séparé d'un autre par sept unités de temps. | ||
Ligne 160 : | Ligne 126 : | ||
TIMSK3 = (1 << OCIE3A); // Comparaison du compteur avec OCR1A | TIMSK3 = (1 << OCIE3A); // Comparaison du compteur avec OCR1A | ||
} | } | ||
unsigned long time_counter = 0; ///< Global time counter incremented by timer interrupts | |||
/** | /** | ||
Ligne 173 : | Ligne 141 : | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Enfin, il est bon de noter que puisque nous nous servions d'un timer pour mesurer le temps et d'un système de communication avec le port série, l'utilisation de delais dans le code était prohibé puisque nous ne devions pas ralentir l'execution du code et uniquement nous reposer sur cette variable compteur. | |||
Avec ces informations en notre possession, nous étions capable d'avoir une mesure du temps précise qui nous permetterai de savoir si l'utilisateur cherche à faire un son court (appui court sur le bouton) ou un son long (appui long). Ces données sont également cruciales lors de l'émission d'un message morse par la carte, puisque celle-ci doit émettre un ensemble de sons longs et courts en préservant les bonnes durées d'espacement entre les lettres et les mots afin que le mot soit audible et compréhensible. | |||
Nous pouvions nous atteler à l'implémentation de notre projet. | |||
= Code = | |||
Pour développer notre t, il nous fallait un moyen de représenter le code morse, et donc des structures :<syntaxhighlight lang="c" line="1"> | |||
typedef struct | |||
{ | |||
char character; | |||
const char *code; | |||
} CharToMorseEntry; | |||
typedef struct | |||
{ | |||
const char *code; | |||
char character; | |||
} MorseToCharEntry; | |||
</syntaxhighlight>Nous avons également définis les valeurs associées du code morse international afin de pouvoir faire la correspondance entre le français et le code morse associé et inversement (puisque chaque lettre a un code qui lui est propre) :<syntaxhighlight lang="c" line="1"> | |||
CharToMorseEntry char_to_morse_table[] = { | |||
{'A', ".-"}, {'B', "-..."}, {'C', "-.-."} /* autres....*/ }}; | |||
MorseToCharEntry morse_to_char_table[] = { | |||
{".-", 'A'}, {"-...", 'B'}, {"-.-.", 'C'} /* autres....*/}; | |||
</syntaxhighlight>Ensuite nous avons définis différentes fonctions utilitaires afin de transformer les caractères en morse et inversement, de les lire, de mettre à jour des tableaux, comme en témoigne ces signatures de méthodes (leur documentation détaillée est disponible dans le code et nous n'allons pas nous attarder sur ceci).<syntaxhighlight lang="c" line="1"> | |||
const char *get_morse_from_char(char c); | |||
char get_char_from_morse(const char *morse); | |||
void get_morse_from_text(const char *input, char *output, size_t max_size); | |||
bool is_character_is_valid_morse(char c); | |||
void update_morse_array(char *morse_array, int *char_index, char character); | |||
void update_message_array(char *message_array, int *char_index, char character); | |||
void reinitilize_array(char *array, int max_index); | |||
</syntaxhighlight>Nous avons ajouté des fonctions pour contrôler la partie hardware du programme, tel que le buzzer, l'initialisation du timer, etc.<syntaxhighlight lang="c" line="1"> | |||
void initiliaze_timer(int diviseur, long periode); | |||
void initialize_morse_game(void); | |||
void use_buzzer(void); | |||
</syntaxhighlight>La partie la plus complexe réside finalement dans ces fonctions qui sont les principales du projet :<syntaxhighlight lang="c" line="1"> | |||
int main(void); | |||
char translate_morse_to_char(bool reset_message); | |||
bool translate_text_to_morse(const char *message, const char *morse_message, bool reset_message); | |||
</syntaxhighlight>La fonction <code>translate_morse_to_char</code> permet de réaliser la traduction de morse en texte. C'est la fonction qui est chargée de détecter lorsque le bouton est appuyé et la durée pendant laquelle il est appuyé. L'idée est que cette fonction soit appelée depuis la boucle principale et comme <syntaxhighlight lang="c" line="1"> | |||
/** | |||
* @brief Converts button presses into Morse code and translates it into characters. | |||
* | |||
* This function interprets button presses as Morse code input, translates the Morse code | |||
* into characters, and manages the state of the translation process. It also handles | |||
* resetting the translation state when requested. | |||
* | |||
* @param reset_message If true, resets the translation state and clears buffers. | |||
* @return The translated character if a complete Morse code sequence is detected, | |||
* or '\0' if no character is ready. | |||
*/ | |||
char translate_morse_to_char(bool reset_message) | |||
{ | |||
static unsigned long button_time_pressed = 0; ///< Duration the button has been pressed. | |||
static unsigned long button_time_not_pressed = 0; ///< Duration the button has not been pressed. | |||
static unsigned long elapsed_time = 0; ///< Elapsed time since the last state change. | |||
static char morse_buffer[MAX_MORSE_CHAR_LEN]; ///< Buffer to store the current Morse code sequence. | |||
static int morse_index = 0; ///< Current index in the Morse buffer. | |||
static char message_buffer[MAX_MESSAGE_LEN]; ///< Buffer to store the translated message. | |||
static int index_message_array = 0; ///< Current index in the message buffer. | |||
static bool has_been_pressed = false; ///< Tracks if the button has been pressed. | |||
static bool has_not_been_pressed = false; ///< Tracks if the button has not been pressed. | |||
static bool was_reseted = false; ///< Tracks if the translation state was reset. | |||
static char last_character; ///< Stores the last translated character. | |||
// Reset the translation state if requested | |||
if (reset_message) | |||
{ | |||
reinitilize_array(message_buffer, sizeof(message_buffer)); | |||
reinitilize_array(morse_buffer, sizeof(morse_buffer)); | |||
index_message_array = 0; | |||
morse_index = 0; | |||
last_character = '\0'; | |||
was_reseted = true; | |||
return '\0'; | |||
} | |||
// Handle buffer overflow by shifting the message buffer | |||
if (index_message_array == MAX_MESSAGE_LEN) | |||
{ | |||
int characters_to_display = MAX_MESSAGE_LEN / 2; | |||
for (int i = 0; i < characters_to_display; i++) | |||
{ | |||
message_buffer[i] = message_buffer[i + characters_to_display]; | |||
} | |||
index_message_array = characters_to_display; | |||
for (int i = characters_to_display; i < MAX_MESSAGE_LEN; i++) | |||
{ | |||
message_buffer[i] = '\0'; | |||
} | |||
} | |||
// Check if the button is not pressed | |||
if (PINC & B0) | |||
{ | |||
PORTE &= ~LED1; // Turn off LED1 | |||
has_been_pressed = false; | |||
if (!has_not_been_pressed) | |||
{ | |||
has_not_been_pressed = true; | |||
elapsed_time = time_counter; | |||
} | |||
button_time_not_pressed = (time_counter - elapsed_time) * COUNTER_PERIOD; | |||
if (was_reseted) | |||
{ | |||
was_reseted = false; | |||
button_time_pressed = 0; | |||
} | |||
// Handle Morse code input based on button press duration | |||
if (button_time_pressed != 0) | |||
{ | |||
update_morse_array(morse_buffer, &morse_index, button_time_pressed < DASH_DURATION ? '.' : '-'); | |||
button_time_pressed = 0; | |||
} | |||
else if (button_time_not_pressed > WORD_PAUSE && index_message_array > 0 && last_character != ' ') | |||
{ | |||
last_character = ' '; | |||
update_message_array(message_buffer, &index_message_array, last_character); | |||
display_message_last_lines(message_buffer, index_message_array); | |||
return last_character; | |||
} | |||
else if (button_time_not_pressed > LETTER_PAUSE && morse_index > 0) | |||
{ | |||
elapsed_time = time_counter; | |||
morse_buffer[morse_index] = '\0'; | |||
last_character = get_char_from_morse(morse_buffer); | |||
reinitilize_array(morse_buffer, morse_index); | |||
morse_index = 0; | |||
update_message_array(message_buffer, &index_message_array, last_character); | |||
display_message_last_lines(message_buffer, index_message_array); | |||
return last_character; | |||
} | |||
} | |||
else | |||
{ | |||
PORTE |= LED1; // Turn on LED1 | |||
use_buzzer(); | |||
has_not_been_pressed = false; | |||
if (!has_been_pressed) | |||
{ | |||
has_been_pressed = true; | |||
elapsed_time = time_counter; | |||
} | |||
button_time_pressed = (time_counter - elapsed_time) * COUNTER_PERIOD; | |||
button_time_not_pressed = 0; | |||
// Reset the translation state if the button is held for a long duration | |||
if (button_time_pressed > RESET_PAUSE) | |||
{ | |||
reinitilize_array(message_buffer, index_message_array); | |||
reinitilize_array(morse_buffer, morse_index); | |||
index_message_array = 0; | |||
morse_index = 0; | |||
last_character = '\0'; | |||
display_message_last_lines(message_buffer, index_message_array); | |||
was_reseted = true; | |||
} | |||
} | |||
return '\0'; | |||
} | |||
</syntaxhighlight> | |||
Cette fonction est particulièrement complexe car on ne doit pas seulement compter le temps pendant lequel le bouton d'émission est appuyé par l'utilisateur mais également le temps pendant lequel le bouton n'est pas appuyé, car en fonction de ce temps là, nous saurons si un espace doit être placé après le dernier caractère (au delà de sept unités de temps écoulé il s'agit normalement d'un nouveau mot). | |||
Pour ce faire, on procède au calcul du temps à chaque tour de boucle afin d'enregistrer le temps qui s'est écoulé depuis la dernière action :<syntaxhighlight lang="c" line="1"> | |||
button_time_not_pressed = (time_counter - elapsed_time) * COUNTER_PERIOD; | |||
</syntaxhighlight> | |||
Ensuite en fonction du temps d'appui on est capables de déterminer s'il sagit d'un point ou d'un trait. On peut alors placer tout cela dans un tableau de caractères, et une fois la saisie espacée par un grand blanc sans appui (3 unités de temps), on peut alors essayer de décoder la lettre entrée par l'utilisateur. On peut alors l'ajouter au message décodé, la fonction retourne la nouvelle lettre décodée et tout peut alors recommencer. Un espace est automatiquement placé dans la chaine après sept unités de temps sans appui. | |||
Pour l'émission d'un message en morse, le fonctionnement est similaire.<syntaxhighlight lang="c" line="1"> | |||
/** | |||
* @brief Converts a text message into Morse code and handles real-time display. | |||
* | |||
* This function translates a given text message into Morse code and manages the | |||
* timing and display of the translation process. It supports resetting the translation | |||
* state and handles the timing for Morse code signals (dots, dashes, and pauses). | |||
* | |||
* @param message The input text message to be translated. | |||
* @param morse_message The output Morse code message. | |||
* @param reset_message If true, resets the translation state and clears buffers. | |||
* @return True if the translation is complete, false otherwise. | |||
*/ | |||
bool translate_text_to_morse(const char *message, const char *morse_message, bool reset_message) | |||
{ | |||
static int translation_morse_index = 0; ///< Current index in the Morse message being translated. | |||
static int message_letter_index = 0; ///< Current index in the original text message. | |||
static unsigned long last_time = 0; ///< Timestamp of the last translation step. | |||
static bool is_translating = true; ///< Indicates if the translation is in progress. | |||
static bool is_new_letter = true; ///< Indicates if a new letter is being processed. | |||
static char partial_message[MAX_MESSAGE_LEN]; ///< Buffer for the partially translated message. | |||
// Reset the translation state if requested | |||
if (reset_message) | |||
{ | |||
translation_morse_index = 0; | |||
message_letter_index = 0; | |||
last_time = 0; | |||
is_translating = true; | |||
reinitilize_array(partial_message, sizeof(partial_message)); | |||
return true; | |||
} | |||
// Initialize the last_time variable if it hasn't been set | |||
if (last_time == 0) | |||
{ | |||
last_time = time_counter * COUNTER_PERIOD; | |||
} | |||
// Calculate the elapsed time since the last translation step | |||
unsigned long elapsed = time_counter * COUNTER_PERIOD - last_time; | |||
// Get the current Morse character being processed | |||
char current_char = morse_message[translation_morse_index]; | |||
if (is_translating) | |||
{ | |||
// Handle the active signal (dot or dash) | |||
if ((current_char == '-' && elapsed < DASH_DURATION) || | |||
(current_char == '.' && elapsed < MORSE_PERIOD)) | |||
{ | |||
PORTE |= LED1; // Turn on LED1 to indicate an active signal | |||
use_buzzer(); // Activate the buzzer | |||
is_new_letter = true; | |||
return false; | |||
} | |||
// Handle pauses between letters or words | |||
else if ((current_char == ' ' && elapsed < LETTER_PAUSE - MORSE_PERIOD) || | |||
(current_char == '/' && elapsed < WORD_PAUSE - MORSE_PERIOD)) | |||
{ | |||
PORTE &= ~LED1; // Turn off LED1 during pauses | |||
if (is_new_letter) | |||
{ | |||
is_new_letter = false; | |||
message_letter_index += (current_char == '/') ? 2 : 1; | |||
strncpy(partial_message, message, message_letter_index); | |||
display_message_last_lines(partial_message, message_letter_index); | |||
} | |||
return false; | |||
} | |||
// Handle the end of the Morse message | |||
else if (current_char == '\0') | |||
{ | |||
if (is_new_letter) | |||
{ | |||
is_new_letter = false; | |||
message_letter_index += (current_char == '/') ? 2 : 1; | |||
strncpy(partial_message, message, message_letter_index); | |||
display_message_last_lines(partial_message, message_letter_index); | |||
} | |||
return true; | |||
} | |||
// Move to the next Morse character | |||
else | |||
{ | |||
PORTE &= ~LED1; // Turn off LED1 | |||
translation_morse_index++; | |||
is_translating = false; | |||
last_time = time_counter * COUNTER_PERIOD; | |||
} | |||
} | |||
else | |||
{ | |||
// Wait for the pause duration before resuming translation | |||
if (elapsed >= MORSE_PERIOD) | |||
{ | |||
is_translating = true; | |||
last_time = time_counter * COUNTER_PERIOD; | |||
} | |||
} | |||
return false; | |||
} | |||
</syntaxhighlight> | |||
La fonction reçoit une chaîne de caractères | |||
= Lancement = | = Lancement = | ||
Pour lancer le projet, il faut insérer la carte dans l'ordinateur et se placer dans le dossier <code>I2L/VirtualSerial</code> à la racine du projet. Depuis cet endroit, il faudra lancer la commande de compilation qui va appeler les makefiles et transférer le code dans la carte : | Pour lancer le projet, il faut insérer la carte dans l'ordinateur et se placer dans le dossier <code>I2L/VirtualSerial</code> à la racine du projet. Depuis cet endroit, il faudra lancer la commande de compilation qui va appeler les makefiles et transférer le code dans la carte : | ||
<code>sudo make dfu</code> | <code>sudo make dfu</code> | ||
Une fois cette commande effectuée vous serez en mesurer de texter en morse comme l'illustre les vidéos ci-dessous! | Une fois cette commande effectuée vous serez en mesurer de texter en morse comme l'illustre les vidéos ci-dessous! | ||
= Démonstrations = | = Démonstrations = |
Version du 1 avril 2025 à 15:48
Proposition de système
Ce projet consiste à concevoir un simulateur de Morse basé sur un microcontrôleur ATmega32U4. Il permet la traduction :
- Morse → Texte : L'utilisateur entre un code Morse via un bouton poussoir, et la carte le convertit en texte affiché sur le PC via une connexion USB série. On peut imaginer également afficher en temps réel les caractères entrés dans un écran LCD, dans le cas où il n'y a aucune connexion avec un poste fixe.
- Texte → Morse : Le PC envoie un message via USB, et la carte le traduit en signaux lumineux (LED) et sonores (buzzer). On peut également imaginer un affichage sur l'écran LCD.
Le circuit comprend :
- Un bouton poussoir pour saisir le Morse.
- Une LED et un buzzer pour la restitution des signaux.
- Un écran LCD pour afficher le message saisi en morse.
- Une interface USB pour la communication avec un PC.
Contre-proposition
OK pour le système embarqué décrit.
Pas de difficulté de programmation au niveau du système embarqué pour le buzzer et le bouton (gestion classique d'entrée et de sortie).
Pour la gestion de l'écran LCD vous pouvez vous inspirer du code des groupes 3 et 4 I2L en 2023/2024.
Pour la gestion série au niveau du système embarqué vous pouvez vous inspirer du code du groupe 5 I2L en 2023/2024.
Proposition définitive
Système de communication en port série avec deux boutons : un pour changer le mode (transmission ou réception) et un pour écrire des données en mode transmission.
La led s'allumera en mode transmission et reception, soit lorsque l'utilisateur appuie sur le bouton en transmission, soit lors de la traduction en mode réception et le buzzer émettera du son en conséquence. Les caratères traduits seront envoyés dans le port USB série et l'écran LCD et le texte à chiffrer en morse sera entré par le port série et affiché au fur et à mesure de la traduction sur l'écran LCD. Un mode de reset permettera de supprimer le texte affiché sur l'écran LCD lorsque le bouton sera maintenu.
Les caractères ASCII et les caractères spéciaux seront gérés, sauf les accents et caractères qui ne font pas partis du code international du morse.
Répartition du travail
Maxime se concentre sur la partie du haut parleur et les boutons poussoirs.
Jérémy réalise le micro controlleur avec les LEDs.
Conclusion : nous avons travaillé tous les deux sur des sujets différents au départ. Jérémy a réussi à réaliser la transmission et la réception USB en port série et Maxime a géré l'affichage sur l'écran LCD. Pour le reste, nous avons travaillé à part égale, ensemble, sur toutes les fonctionnalités spécifiées.
Carte
Schéma initial
- schéma : Fichier:I2L-2024-Carte-G4.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
Carte au 23/02/2025
Non encore réalisé :
- 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.
Carte au 27/02/2025
Carte finalisée avec l'écran LCD et prête à l'emploi.
Explication du fonctionnement du code morse
Pour comprendre la suite, il va nous falloir détailler comment fonctionne le morse, car la mesure du temps est primordiale pour comprendre comment fonctionnent la traduction et le fonctionnement.
Chaque lettre en morse correspond à une suite de points et de traits bien défini. Par exemple, le signe SOS s'écrirait ... --- ...
en morse, puisque le S est composé de 3 points et le O de trois traits, comme inscrit dans le code international du morse.
Un point représente une unité de temps et un trait représente trois unités de temps. Chaque point ou trait est séparé d'une unité de temps. Chaque lettre est séparé de trois unités de temps soit l'équivalent d'un trait, et un mot est séparé d'un autre par sept unités de temps.
Cela signifie donc qu'un mot comme SOS prendrait 27 unités de temps + 7 autres unités de temps si un autre mot est émis ensuite. Pour arriver à ce résultat, il suffit d'aditionner chaque caractères :
- 6 points = 6 unités de temps
- 3 traits = 3 x 3 = 9 unités de temps
- 6 unités de temps entre chaque caractère (sauf entre chaque lettre)
- et 2 x 3 unités de temps séparant les trois lettres
Il nous fallait définir des constantes précises pour l'unité de temps, puis ensuite les calcul se baserai sur le temps défini, sachant que nous ne sommes pas des experts en morse, l'idéal est de mettre des temps élevés pour qu'on arrive à formuler des messages mais à l'époque il fallait aller vite puisque le but était d'écrire vite... Le véritable porblème réside donc dans cette mesure du temps. Nous avons dû utiliser le TIMER3 de notre carte afin d'avoir un compteur de temps qui s'incrémente régulièrement afin de pouvoir facilement détecter les caractères que souhaite faire l'utilisateur.
/**
* @brief Initializes the hardware timer for periodic interrupts.
*
* @param diviseur Clock divider value.
* @param periode Timer period in milliseconds.
*/
void initiliaze_timer(int diviseur, long periode)
{
CLKSEL0 = 0b00010101; // sélection de l'horloge externe
CLKSEL1 = 0b00001111; // minimum de 8Mhz
CLKPR = 0b10000000; // modification du diviseur d'horloge (CLKPCE=1)
CLKPR = 0; // 0 pour pas de diviseur (diviseur de 1)
TCCR3A = 0;
TCCR3B |= (1 << WGM32); // Le mode choisi n'utilise pas ce registre
switch (diviseur)
{
case 8:
TCCR3B |= (1 << CS31);
break;
case 64:
TCCR3B |= (1 << CS31 | 11 << CS30);
break;
case 256:
TCCR3B |= (1 << CS32);
break;
case 1024:
TCCR3B |= (1 << CS32 | 1 << CS30);
break;
}
// Un cycle prend 1/F_CPU secondes.
// Un pas de compteur prend diviseur/F_CPU secondes.
// Pour une periode en millisecondes, il faut (periode/1000)/(diviseur/F_CPU) pas
// soit (periode*F_CPU)/(1000*diviseur)
OCR3A = F_CPU / PERIOD * periode / diviseur; // Calcul du pas
TCNT3 = 0; // Compteur initialisé
TIMSK3 = (1 << OCIE3A); // Comparaison du compteur avec OCR1A
}
unsigned long time_counter = 0; ///< Global time counter incremented by timer interrupts
/**
* @brief Timer interrupt service routine.
*
* Increments the global time counter.
*/
ISR(TIMER3_COMPA_vect)
{
time_counter++;
}
Enfin, il est bon de noter que puisque nous nous servions d'un timer pour mesurer le temps et d'un système de communication avec le port série, l'utilisation de delais dans le code était prohibé puisque nous ne devions pas ralentir l'execution du code et uniquement nous reposer sur cette variable compteur.
Avec ces informations en notre possession, nous étions capable d'avoir une mesure du temps précise qui nous permetterai de savoir si l'utilisateur cherche à faire un son court (appui court sur le bouton) ou un son long (appui long). Ces données sont également cruciales lors de l'émission d'un message morse par la carte, puisque celle-ci doit émettre un ensemble de sons longs et courts en préservant les bonnes durées d'espacement entre les lettres et les mots afin que le mot soit audible et compréhensible.
Nous pouvions nous atteler à l'implémentation de notre projet.
Code
Pour développer notre t, il nous fallait un moyen de représenter le code morse, et donc des structures :
typedef struct
{
char character;
const char *code;
} CharToMorseEntry;
typedef struct
{
const char *code;
char character;
} MorseToCharEntry;
Nous avons également définis les valeurs associées du code morse international afin de pouvoir faire la correspondance entre le français et le code morse associé et inversement (puisque chaque lettre a un code qui lui est propre) :
CharToMorseEntry char_to_morse_table[] = {
{'A', ".-"}, {'B', "-..."}, {'C', "-.-."} /* autres....*/ }};
MorseToCharEntry morse_to_char_table[] = {
{".-", 'A'}, {"-...", 'B'}, {"-.-.", 'C'} /* autres....*/};
Ensuite nous avons définis différentes fonctions utilitaires afin de transformer les caractères en morse et inversement, de les lire, de mettre à jour des tableaux, comme en témoigne ces signatures de méthodes (leur documentation détaillée est disponible dans le code et nous n'allons pas nous attarder sur ceci).
const char *get_morse_from_char(char c);
char get_char_from_morse(const char *morse);
void get_morse_from_text(const char *input, char *output, size_t max_size);
bool is_character_is_valid_morse(char c);
void update_morse_array(char *morse_array, int *char_index, char character);
void update_message_array(char *message_array, int *char_index, char character);
void reinitilize_array(char *array, int max_index);
Nous avons ajouté des fonctions pour contrôler la partie hardware du programme, tel que le buzzer, l'initialisation du timer, etc.
void initiliaze_timer(int diviseur, long periode);
void initialize_morse_game(void);
void use_buzzer(void);
La partie la plus complexe réside finalement dans ces fonctions qui sont les principales du projet :
int main(void);
char translate_morse_to_char(bool reset_message);
bool translate_text_to_morse(const char *message, const char *morse_message, bool reset_message);
La fonction translate_morse_to_char
permet de réaliser la traduction de morse en texte. C'est la fonction qui est chargée de détecter lorsque le bouton est appuyé et la durée pendant laquelle il est appuyé. L'idée est que cette fonction soit appelée depuis la boucle principale et comme
/**
* @brief Converts button presses into Morse code and translates it into characters.
*
* This function interprets button presses as Morse code input, translates the Morse code
* into characters, and manages the state of the translation process. It also handles
* resetting the translation state when requested.
*
* @param reset_message If true, resets the translation state and clears buffers.
* @return The translated character if a complete Morse code sequence is detected,
* or '\0' if no character is ready.
*/
char translate_morse_to_char(bool reset_message)
{
static unsigned long button_time_pressed = 0; ///< Duration the button has been pressed.
static unsigned long button_time_not_pressed = 0; ///< Duration the button has not been pressed.
static unsigned long elapsed_time = 0; ///< Elapsed time since the last state change.
static char morse_buffer[MAX_MORSE_CHAR_LEN]; ///< Buffer to store the current Morse code sequence.
static int morse_index = 0; ///< Current index in the Morse buffer.
static char message_buffer[MAX_MESSAGE_LEN]; ///< Buffer to store the translated message.
static int index_message_array = 0; ///< Current index in the message buffer.
static bool has_been_pressed = false; ///< Tracks if the button has been pressed.
static bool has_not_been_pressed = false; ///< Tracks if the button has not been pressed.
static bool was_reseted = false; ///< Tracks if the translation state was reset.
static char last_character; ///< Stores the last translated character.
// Reset the translation state if requested
if (reset_message)
{
reinitilize_array(message_buffer, sizeof(message_buffer));
reinitilize_array(morse_buffer, sizeof(morse_buffer));
index_message_array = 0;
morse_index = 0;
last_character = '\0';
was_reseted = true;
return '\0';
}
// Handle buffer overflow by shifting the message buffer
if (index_message_array == MAX_MESSAGE_LEN)
{
int characters_to_display = MAX_MESSAGE_LEN / 2;
for (int i = 0; i < characters_to_display; i++)
{
message_buffer[i] = message_buffer[i + characters_to_display];
}
index_message_array = characters_to_display;
for (int i = characters_to_display; i < MAX_MESSAGE_LEN; i++)
{
message_buffer[i] = '\0';
}
}
// Check if the button is not pressed
if (PINC & B0)
{
PORTE &= ~LED1; // Turn off LED1
has_been_pressed = false;
if (!has_not_been_pressed)
{
has_not_been_pressed = true;
elapsed_time = time_counter;
}
button_time_not_pressed = (time_counter - elapsed_time) * COUNTER_PERIOD;
if (was_reseted)
{
was_reseted = false;
button_time_pressed = 0;
}
// Handle Morse code input based on button press duration
if (button_time_pressed != 0)
{
update_morse_array(morse_buffer, &morse_index, button_time_pressed < DASH_DURATION ? '.' : '-');
button_time_pressed = 0;
}
else if (button_time_not_pressed > WORD_PAUSE && index_message_array > 0 && last_character != ' ')
{
last_character = ' ';
update_message_array(message_buffer, &index_message_array, last_character);
display_message_last_lines(message_buffer, index_message_array);
return last_character;
}
else if (button_time_not_pressed > LETTER_PAUSE && morse_index > 0)
{
elapsed_time = time_counter;
morse_buffer[morse_index] = '\0';
last_character = get_char_from_morse(morse_buffer);
reinitilize_array(morse_buffer, morse_index);
morse_index = 0;
update_message_array(message_buffer, &index_message_array, last_character);
display_message_last_lines(message_buffer, index_message_array);
return last_character;
}
}
else
{
PORTE |= LED1; // Turn on LED1
use_buzzer();
has_not_been_pressed = false;
if (!has_been_pressed)
{
has_been_pressed = true;
elapsed_time = time_counter;
}
button_time_pressed = (time_counter - elapsed_time) * COUNTER_PERIOD;
button_time_not_pressed = 0;
// Reset the translation state if the button is held for a long duration
if (button_time_pressed > RESET_PAUSE)
{
reinitilize_array(message_buffer, index_message_array);
reinitilize_array(morse_buffer, morse_index);
index_message_array = 0;
morse_index = 0;
last_character = '\0';
display_message_last_lines(message_buffer, index_message_array);
was_reseted = true;
}
}
return '\0';
}
Cette fonction est particulièrement complexe car on ne doit pas seulement compter le temps pendant lequel le bouton d'émission est appuyé par l'utilisateur mais également le temps pendant lequel le bouton n'est pas appuyé, car en fonction de ce temps là, nous saurons si un espace doit être placé après le dernier caractère (au delà de sept unités de temps écoulé il s'agit normalement d'un nouveau mot).
Pour ce faire, on procède au calcul du temps à chaque tour de boucle afin d'enregistrer le temps qui s'est écoulé depuis la dernière action :
button_time_not_pressed = (time_counter - elapsed_time) * COUNTER_PERIOD;
Ensuite en fonction du temps d'appui on est capables de déterminer s'il sagit d'un point ou d'un trait. On peut alors placer tout cela dans un tableau de caractères, et une fois la saisie espacée par un grand blanc sans appui (3 unités de temps), on peut alors essayer de décoder la lettre entrée par l'utilisateur. On peut alors l'ajouter au message décodé, la fonction retourne la nouvelle lettre décodée et tout peut alors recommencer. Un espace est automatiquement placé dans la chaine après sept unités de temps sans appui.
Pour l'émission d'un message en morse, le fonctionnement est similaire.
/**
* @brief Converts a text message into Morse code and handles real-time display.
*
* This function translates a given text message into Morse code and manages the
* timing and display of the translation process. It supports resetting the translation
* state and handles the timing for Morse code signals (dots, dashes, and pauses).
*
* @param message The input text message to be translated.
* @param morse_message The output Morse code message.
* @param reset_message If true, resets the translation state and clears buffers.
* @return True if the translation is complete, false otherwise.
*/
bool translate_text_to_morse(const char *message, const char *morse_message, bool reset_message)
{
static int translation_morse_index = 0; ///< Current index in the Morse message being translated.
static int message_letter_index = 0; ///< Current index in the original text message.
static unsigned long last_time = 0; ///< Timestamp of the last translation step.
static bool is_translating = true; ///< Indicates if the translation is in progress.
static bool is_new_letter = true; ///< Indicates if a new letter is being processed.
static char partial_message[MAX_MESSAGE_LEN]; ///< Buffer for the partially translated message.
// Reset the translation state if requested
if (reset_message)
{
translation_morse_index = 0;
message_letter_index = 0;
last_time = 0;
is_translating = true;
reinitilize_array(partial_message, sizeof(partial_message));
return true;
}
// Initialize the last_time variable if it hasn't been set
if (last_time == 0)
{
last_time = time_counter * COUNTER_PERIOD;
}
// Calculate the elapsed time since the last translation step
unsigned long elapsed = time_counter * COUNTER_PERIOD - last_time;
// Get the current Morse character being processed
char current_char = morse_message[translation_morse_index];
if (is_translating)
{
// Handle the active signal (dot or dash)
if ((current_char == '-' && elapsed < DASH_DURATION) ||
(current_char == '.' && elapsed < MORSE_PERIOD))
{
PORTE |= LED1; // Turn on LED1 to indicate an active signal
use_buzzer(); // Activate the buzzer
is_new_letter = true;
return false;
}
// Handle pauses between letters or words
else if ((current_char == ' ' && elapsed < LETTER_PAUSE - MORSE_PERIOD) ||
(current_char == '/' && elapsed < WORD_PAUSE - MORSE_PERIOD))
{
PORTE &= ~LED1; // Turn off LED1 during pauses
if (is_new_letter)
{
is_new_letter = false;
message_letter_index += (current_char == '/') ? 2 : 1;
strncpy(partial_message, message, message_letter_index);
display_message_last_lines(partial_message, message_letter_index);
}
return false;
}
// Handle the end of the Morse message
else if (current_char == '\0')
{
if (is_new_letter)
{
is_new_letter = false;
message_letter_index += (current_char == '/') ? 2 : 1;
strncpy(partial_message, message, message_letter_index);
display_message_last_lines(partial_message, message_letter_index);
}
return true;
}
// Move to the next Morse character
else
{
PORTE &= ~LED1; // Turn off LED1
translation_morse_index++;
is_translating = false;
last_time = time_counter * COUNTER_PERIOD;
}
}
else
{
// Wait for the pause duration before resuming translation
if (elapsed >= MORSE_PERIOD)
{
is_translating = true;
last_time = time_counter * COUNTER_PERIOD;
}
}
return false;
}
La fonction reçoit une chaîne de caractères
Lancement
Pour lancer le projet, il faut insérer la carte dans l'ordinateur et se placer dans le dossier I2L/VirtualSerial
à la racine du projet. Depuis cet endroit, il faudra lancer la commande de compilation qui va appeler les makefiles et transférer le code dans la carte :
sudo make dfu
Une fois cette commande effectuée vous serez en mesurer de texter en morse comme l'illustre les vidéos ci-dessous!
Démonstrations
Voici deux vidéo, la première montre comment il est possible d'écrire du texte en morse (même si c'est très dur on ne dira pas qu'on a enregistré la vidéo plusieurs fois) et la deuxième, ce que représente en morse un texte écris dans un port série USB.
Rendus
Projet KiCAD : Fichier:I2L-2024-Carte-G4-rex.zip
Programmes : Fichier:I2L-2024-Programmes-G4.zip