« I2L 2025 Groupe1 » : différence entre les versions
(→Carte) |
|||
| (15 versions intermédiaires par 2 utilisateurs non affichées) | |||
| Ligne 7 : | Ligne 7 : | ||
Le périphérique USB à émuler est un clavier. Vous pourrez simuler plus de touches que disponibles sur la carte électronique en utilisant des menus sur l'écran intégré à la carte. | Le périphérique USB à émuler est un clavier. Vous pourrez simuler plus de touches que disponibles sur la carte électronique en utilisant des menus sur l'écran intégré à la carte. | ||
Une bibliothèque pour gérer le contrôleur graphique SDD1306 est disponible [https://github.com/lexus2k/ssd1306]. | |||
= Proposition définitive (étudiants) = | = Proposition définitive (étudiants) = | ||
| Ligne 29 : | Ligne 31 : | ||
Au 9 octobre 2025, il manque un écran OLED. | Au 9 octobre 2025, il manque un écran OLED. | ||
Au 15 octobre 2025, l'écran OLED est disponible. | |||
== Carte réalisée (intervenant) == | == Carte réalisée (intervenant) == | ||
[[File:ISL-2025-Carte-G1.jpg|thumb|center|400px|Photo de la carte]] | [[File:ISL-2025-Carte-G1.jpg|thumb|center|400px|Photo de la carte]] | ||
La carte est entiérement soudée. Eventuellement vous pouvez demander l'ajout d'un buzzer. | |||
= Travaux (étudiants) = | = Travaux (étudiants) = | ||
'''- 13/10/2025 :''' | |||
Programmation des boutons pour activer les différentes led. | |||
Les boutons haut, bas, gauche et droite allument chacun une led. Chaque LED reste allumée tant que le bouton est appuyé. | |||
Le bouton du milieu permet lui d'allumer les 4 LED, elles restent allumées tant qu'il est appuyé. Lorsque le bouton est laché, un delai d'une seconde est activé avant d'éteindre les LED. | |||
+ début joystick | |||
Pour avoir des valeurs de délai correctes, l'AVR utilise une fréquence de 8Mhz sans diviseur. | |||
'''- 16/10/2025 :''' | |||
Programmation du joystick (bouton et mouvement) pour allumer les LED et activer le buzzer. Les LED sont progressivement allumées et la fréquence du buzzer augmente en fonction de l'avancée du joystick. | |||
Programmation de l'écran OLED. On affiche sur l'écran une barre de progression ainsi que la "vitesse" du joystick en fonction de son avancée. | |||
[[Fichier:Vidéo joystick + écran OLED.mp4|vignette|centré|Vidéo joystick + écran OLED]]'''- 13/11/2025 :''' | |||
Création du menu sur l'écran OLED avec différents menus. | |||
Les mouvements sur le joystick permettent de se déplacer dans les menus. | |||
Le menu principal permet de sélectionner l'application sur laquelle on souhaite effectuer les actions (pour le moment Spotify uniquement). Ensuite un sous menu s'ouvre avec les actions disponibles sur l'application sélectionnée. | |||
Pour le moment lors de la sélection d'une action, un simple texte s'affiche pendant 1 seconde avec le nom de l'action sélectionnée. Cela sera ensuite remplacée par une action envoyée au PC. | |||
[[Fichier:Menus écran OLED.mov|centré|vignette|Menus écran OLED]] | |||
Fin de la séance : | |||
Nous avons ajouté la possibilité de naviguer dans les menus avec les boutons. | |||
Nous avons essayé d'ajouter LUFA sur le projet. Cependant, lorsque nous mettons le programme sur le controleur, celui ci n'est pas listé via la commande "lsusb". | |||
'''- 27/01/2026 :''' | |||
Nous avons ajouté LUFA sur le projet pour que le PC puisse détecter le micro controleur comme un Clavier et un controleur de média. | |||
Nous avons actuellement un menu principal qui s'affiche sur l'écran avec 2 sous menu : Media et General | |||
Le sous menu média propose les actions suivantes : | |||
- Jouer/Mettre en pause le média actuel | |||
- Passer au média suivant | |||
- Revenir au média précédent | |||
- Augmenter le volume du systeme | |||
- Diminuer le volume du systeme | |||
- Couper/Activer le son du systeme | |||
Le sous menu général propose les actions suivantes : | |||
- Copier (Ctrl + C) | |||
- Coller (Ctrl + V) | |||
- Tout sélectionner (Ctrl + A) | |||
- Rechercher (Ctrl + F) | |||
Pour les contrôles média, le micro controleur envoie des commandes en direct (Play/Pause, Volume up, ...) que le système d'exploitation interprète. | |||
Pour les contrôles généraux, le micro controleur envoie des raccourcis clavier qui sont interprétés par le système d'exploitation comme si on utilisait la combinaison sur un clavier. | |||
Pour la navigation dans les menus, celle-ci est possible grâce au joystick et/ou aux différents boutons : | |||
Naviguer vers le haut : joystick vers le haut ou appui sur le bouton du haut | |||
Naviguer vers le bas : joystick vers le bas ou appui sur le bouton du bas | |||
Accéder au sous menu / effectuer l'action : joystick vers la droite ou appui sur le bouton du milieu ou appui sur le bouton de droite | |||
Retour en arrière : joystick vers la gauche ou appui sur le bouton de gauche | |||
[[Fichier:Contrôles média.mov|alt=Video de démonstration des contrôles de média|centré|vignette|Contrôles média]] | |||
[[Fichier:Contrôles généraux (raccourcis clavier).mov|alt=Vidéo de démonstration des contrôles généraux|centré|vignette|Contrôles généraux (raccourcis clavier)]] | |||
= Extraits significatifs de code (étudiants) = | = Extraits significatifs de code (étudiants) = | ||
'''Gestion des mouvements du joystick et des appuis sur les boutons'''<syntaxhighlight lang="c" line="1"> | |||
int main(void) | |||
{ | |||
SetupHardware(); | |||
// LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY); | |||
GlobalInterruptEnable(); | |||
bool btn_bottom_pressed = false; | |||
bool btn_top_pressed = false; | |||
bool btn_left_pressed = false; | |||
bool btn_right_pressed = false; | |||
bool btn_mid_pressed = false; | |||
// Joystick state flags to prevent spurious input at startup. | |||
bool joystick_up_active = true; | |||
bool joystick_down_active = true; | |||
bool joystick_left_active = true; | |||
bool joystick_right_active = true; | |||
for (;;) | |||
{ | |||
HID_Task(); | |||
USB_USBTask(); | |||
// --- Polling Buttons --- | |||
// BTN_BOTTOM (Next) | |||
if (button_is_pressed(BTN_BOTTOM)) { | |||
if (!btn_bottom_pressed) { | |||
menu_next(); | |||
btn_bottom_pressed = true; | |||
} | |||
} else { | |||
btn_bottom_pressed = false; | |||
} | |||
// BTN_TOP (Prev) | |||
if (button_is_pressed(BTN_TOP)) { | |||
if (!btn_top_pressed) { | |||
menu_prev(); | |||
btn_top_pressed = true; | |||
} | |||
} else { | |||
btn_top_pressed = false; | |||
} | |||
// BTN_LEFT (Back) | |||
if (button_is_pressed(BTN_LEFT)) { | |||
if (!btn_left_pressed) { | |||
menu_back(); | |||
btn_left_pressed = true; | |||
} | |||
} else { | |||
btn_left_pressed = false; | |||
} | |||
// BTN_RIGHT (Select) | |||
if (button_is_pressed(BTN_RIGHT)) { | |||
if (!btn_right_pressed) { | |||
menu_select(); | |||
btn_right_pressed = true; | |||
} | |||
} else { | |||
btn_right_pressed = false; | |||
} | |||
// BTN_MID (Select) | |||
if (button_is_pressed(BTN_MID)) { | |||
if (!btn_mid_pressed) { | |||
menu_select(); | |||
btn_mid_pressed = true; | |||
} | |||
} else { | |||
btn_mid_pressed = false; | |||
} | |||
// --- User Joystick Logic --- | |||
uint16_t x = joystick_read_x(); | |||
uint16_t y = joystick_read_y(); | |||
// Move down | |||
if (y < JOY_DOWN_THRESHOLD && !joystick_down_active) { | |||
menu_next(); | |||
joystick_down_active = true; | |||
} else if (y >= JOY_CENTER_MIN) { | |||
joystick_down_active = false; | |||
} | |||
// Move up | |||
if (y > JOY_UP_THRESHOLD && !joystick_up_active) { | |||
menu_prev(); | |||
joystick_up_active = true; | |||
} else if (y <= JOY_CENTER_MAX) { | |||
joystick_up_active = false; | |||
} | |||
// Select item | |||
if (x < JOY_RIGHT_THRESHOLD && !joystick_right_active) { | |||
menu_select(); | |||
joystick_right_active = true; | |||
} else if (x >= JOY_CENTER_MIN) { | |||
joystick_right_active = false; | |||
} | |||
// Go back | |||
if (x > JOY_LEFT_THRESHOLD && !joystick_left_active) { | |||
menu_back(); | |||
joystick_left_active = true; | |||
} else if (x <= JOY_CENTER_MAX) { | |||
joystick_left_active = false; | |||
} | |||
// --- Display --- | |||
display_current_menu(); | |||
} | |||
} | |||
</syntaxhighlight>'''Lecture de l'état d'un bouton'''<syntaxhighlight lang="c" line="1"> | |||
#include "button.h" | |||
void button_init(void) { | |||
// LEFT, RIGHT, TOP sur PORTB | |||
BTN_LRT_DDR &= ~((1 << BTN_LEFT_PIN) | (1 << BTN_RIGHT_PIN) | (1 << BTN_TOP_PIN)); | |||
BTN_LRT_PORT |= (1 << BTN_LEFT_PIN) | (1 << BTN_RIGHT_PIN) | (1 << BTN_TOP_PIN); | |||
// BOTTOM sur PORTC | |||
BTN_BOTTOM_DDR &= ~(1 << BTN_BOTTOM_PIN); | |||
BTN_BOTTOM_PORT |= (1 << BTN_BOTTOM_PIN); | |||
// MID sur PORTE | |||
BTN_MID_DDR &= ~(1 << BTN_MID_PIN); | |||
BTN_MID_PORT |= (1 << BTN_MID_PIN); | |||
} | |||
uint8_t button_is_pressed(button_t btn) { | |||
switch (btn) { | |||
case BTN_LEFT: | |||
return !(BTN_LRT_PIN_REG & (1 << BTN_LEFT_PIN)); | |||
case BTN_RIGHT: | |||
return !(BTN_LRT_PIN_REG & (1 << BTN_RIGHT_PIN)); | |||
case BTN_TOP: | |||
return !(BTN_LRT_PIN_REG & (1 << BTN_TOP_PIN)); | |||
case BTN_BOTTOM: | |||
return !(BTN_BOTTOM_PIN_REG & (1 << BTN_BOTTOM_PIN)); | |||
case BTN_MID: | |||
return !(BTN_MID_PIN_REG & (1 << BTN_MID_PIN)); | |||
default: | |||
return 0; | |||
} | |||
} | |||
</syntaxhighlight>'''Lecture de l'état du joystick'''<syntaxhighlight lang="c" line="1"> | |||
#include "joystick.h" | |||
void joystick_init(void) { | |||
// === Configurer les broches du joystick en entrée === | |||
JOYSTICK_DDR &= ~((1 << JOYSTICK_X) | (1 << JOYSTICK_Y) | (1 << JOYSTICK_BTN)); | |||
// Activer la résistance de pull-up pour le bouton du joystick | |||
JOYSTICK_PORT |= (1 << JOYSTICK_BTN); | |||
// === Initialisation de l'ADC === | |||
ADMUX = (1 << REFS0); // Référence de tension = AVcc | |||
ADCSRA = (1 << ADEN) // Activer ADC | |||
| (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // Prescaler = 128 | |||
} | |||
static uint16_t adc_read(uint8_t channel) { | |||
ADMUX = (ADMUX & 0xF0) | (channel & 0x0F); // Sélection du canal (PF4 = ADC4, PF5 = ADC5) | |||
ADCSRA |= (1 << ADSC); // Lancer la conversion | |||
while (ADCSRA & (1 << ADSC)); // Attendre la fin | |||
return ADC; // Retourner la valeur sur 10 bits (0–1023) | |||
} | |||
uint16_t joystick_read_x(void) { | |||
return adc_read(JOYSTICK_X); // PF4 → ADC4 | |||
} | |||
uint16_t joystick_read_y(void) { | |||
return adc_read(JOYSTICK_Y); // PF5 → ADC5 | |||
} | |||
uint8_t joystick_is_pressed(void) { | |||
return !(JOYSTICK_PIN_REG & (1 << JOYSTICK_BTN)); // Bouton actif à l'état bas | |||
} | |||
</syntaxhighlight> | |||
'''Affichage, Navigation et actions dans les menus'''<syntaxhighlight lang="c" line="1"> | |||
static Menu* current_menu; | |||
// --- Private Functions --- | |||
static void display_action_feedback(const char* message) { | |||
GLCD_Clear(); | |||
GLCD_GotoXY(10, 28); | |||
GLCD_PrintString((char*)message); | |||
GLCD_Render(); | |||
all_led_on(); | |||
buzzer_on(); | |||
_delay_ms(200); | |||
buzzer_off(); | |||
all_led_off(); | |||
} | |||
// --- Public API Implementation --- | |||
void menu_init(void) { | |||
current_menu = &main_menu; | |||
} | |||
void display_current_menu(void) { | |||
GLCD_Clear(); | |||
GLCD_GotoXY(2, 2); | |||
GLCD_PrintString((char*)current_menu->title); | |||
const uint8_t FONT_HEIGHT = 8; | |||
const uint8_t ITEM_PADDING = 3; | |||
const uint8_t rect_height = FONT_HEIGHT + ITEM_PADDING * 2; | |||
const uint8_t max_visible_items = 3; | |||
for (uint8_t i = 0; i < max_visible_items; i++) { | |||
uint8_t item_index = current_menu->scroll_offset + i; | |||
if (item_index >= current_menu->num_items) break; | |||
uint8_t y_position = 15 + i * rect_height; | |||
if (item_index == current_menu->selected_item) { | |||
GLCD_DrawRectangle(1, y_position, 126, y_position + rect_height - 1, GLCD_Black); | |||
GLCD_GotoXY(5, y_position + ITEM_PADDING); | |||
GLCD_PrintString((char*)current_menu->items[item_index].name); | |||
} else { | |||
GLCD_GotoXY(5, y_position + ITEM_PADDING); | |||
GLCD_PrintString((char*)current_menu->items[item_index].name); | |||
} | |||
} | |||
GLCD_Render(); | |||
} | |||
void menu_next(void) { | |||
if (current_menu->selected_item < current_menu->num_items - 1) { | |||
current_menu->selected_item++; | |||
if (current_menu->selected_item >= current_menu->scroll_offset + 3) { | |||
current_menu->scroll_offset++; | |||
} | |||
} | |||
} | |||
void menu_prev(void) { | |||
if (current_menu->selected_item > 0) { | |||
current_menu->selected_item--; | |||
if (current_menu->selected_item < current_menu->scroll_offset) { | |||
current_menu->scroll_offset = current_menu->selected_item; | |||
} | |||
} | |||
} | |||
void menu_select(void) { | |||
const MenuItem* selected = ¤t_menu->items[current_menu->selected_item]; | |||
if (selected->type == MENU_ITEM_TYPE_SUBMENU && selected->target.submenu != NULL) { | |||
current_menu = selected->target.submenu; | |||
} else if (selected->type == MENU_ITEM_TYPE_ACTION && selected->target.action != NULL) { | |||
selected->target.action(); | |||
display_action_feedback(selected->name); | |||
} | |||
} | |||
void menu_back(void) { | |||
if (current_menu->parent != NULL) { | |||
current_menu = current_menu->parent; | |||
} | |||
} | |||
</syntaxhighlight>'''Déclaration des menus'''<syntaxhighlight lang="c" line="1"> | |||
// --- Action Prototypes --- | |||
void action_media_play_pause(void) { send_media_command(CMD_PLAY_PAUSE); } | |||
void action_media_next(void) { send_media_command(CMD_NEXT_TRACK); } | |||
void action_media_prev(void) { send_media_command(CMD_PREV_TRACK); } | |||
void action_media_vol_up(void) { send_media_command(CMD_VOLUME_UP); } | |||
void action_media_vol_down(void) { send_media_command(CMD_VOLUME_DOWN); } | |||
void action_media_mute(void) { send_media_command(CMD_MUTE); } | |||
void action_general_copy(void) { send_general_command(CMD_COPY); } | |||
void action_general_paste(void) { send_general_command(CMD_PASTE); } | |||
void action_general_select_all(void) { send_general_command(CMD_SELECT_ALL); } | |||
void action_general_search(void) { send_general_command(CMD_SEARCH); } | |||
// --- Menu Definitions --- | |||
// Forward declare menus | |||
extern Menu main_menu; | |||
extern Menu media_menu; | |||
extern Menu settings_menu; | |||
extern Menu code_menu; | |||
// --- General Menu --- | |||
const MenuItem general_menu_items[] = { | |||
{"Copy", MENU_ITEM_TYPE_ACTION, .target.action = &action_general_copy}, | |||
{"Paste", MENU_ITEM_TYPE_ACTION, .target.action = &action_general_paste}, | |||
{"Select all", MENU_ITEM_TYPE_ACTION, .target.action = &action_general_select_all}, | |||
{"Search", MENU_ITEM_TYPE_ACTION, .target.action = &action_general_search}, | |||
}; | |||
Menu general_menu = { | |||
.title = "General", | |||
.items = general_menu_items, | |||
.num_items = sizeof(general_menu_items) / sizeof(MenuItem), | |||
.selected_item = 0, | |||
.scroll_offset = 0, | |||
.parent = &main_menu | |||
}; | |||
// --- Media Menu --- | |||
const MenuItem media_menu_items[] = { | |||
{"Play/Pause", MENU_ITEM_TYPE_ACTION, .target.action = &action_media_play_pause}, | |||
{"Next Track", MENU_ITEM_TYPE_ACTION, .target.action = &action_media_next}, | |||
{"Prev Track", MENU_ITEM_TYPE_ACTION, .target.action = &action_media_prev}, | |||
{"Volume +", MENU_ITEM_TYPE_ACTION, .target.action = &action_media_vol_up}, | |||
{"Volume -", MENU_ITEM_TYPE_ACTION, .target.action = &action_media_vol_down}, | |||
{"Mute", MENU_ITEM_TYPE_ACTION, .target.action = &action_media_mute}, | |||
}; | |||
Menu media_menu = { | |||
.title = "Media", | |||
.items = media_menu_items, | |||
.num_items = sizeof(media_menu_items) / sizeof(MenuItem), | |||
.selected_item = 0, | |||
.scroll_offset = 0, | |||
.parent = &main_menu | |||
}; | |||
// --- Main Menu --- | |||
const MenuItem main_menu_items[] = { | |||
{"Media", MENU_ITEM_TYPE_SUBMENU, .target.submenu = &media_menu}, | |||
{"General", MENU_ITEM_TYPE_SUBMENU, .target.submenu = &general_menu}, | |||
}; | |||
Menu main_menu = { | |||
.title = "Main Menu", | |||
.items = main_menu_items, | |||
.num_items = sizeof(main_menu_items) / sizeof(MenuItem), | |||
.selected_item = 0, | |||
.scroll_offset = 0, | |||
.parent = NULL | |||
}; | |||
</syntaxhighlight>'''Gestion et envoi des actions au PC'''<syntaxhighlight lang="c" line="1"> | |||
#include "actions.h" | |||
#include <avr/io.h> | |||
#include "../Keyboard.h" | |||
// Codes standards USB HID Consumer | |||
#define MEDIA_PLAY_PAUSE 0xCD | |||
#define MEDIA_NEXT_TRACK 0xB5 | |||
#define MEDIA_PREV_TRACK 0xB6 | |||
#define MEDIA_VOLUME_UP 0xE9 | |||
#define MEDIA_VOLUME_DOWN 0xEA | |||
#define MEDIA_MUTE 0xE2 | |||
void send_media_command(MediaCommand_t cmd) { | |||
GLCD_Clear(); | |||
GLCD_GotoXY(0, 20); | |||
uint16_t usbHIDCode = 0; | |||
switch (cmd) { | |||
case CMD_PLAY_PAUSE: | |||
GLCD_PrintString("Play/Pause"); | |||
usbHIDCode = MEDIA_PLAY_PAUSE; | |||
break; | |||
case CMD_NEXT_TRACK: | |||
GLCD_PrintString("Next Track"); | |||
usbHIDCode = MEDIA_NEXT_TRACK; | |||
break; | |||
case CMD_PREV_TRACK: | |||
GLCD_PrintString("Prev Track"); | |||
usbHIDCode = MEDIA_PREV_TRACK; | |||
break; | |||
case CMD_VOLUME_UP: | |||
GLCD_PrintString("Volume Up"); | |||
usbHIDCode = MEDIA_VOLUME_UP; | |||
break; | |||
case CMD_VOLUME_DOWN: | |||
GLCD_PrintString("Volume Down"); | |||
usbHIDCode = MEDIA_VOLUME_DOWN; | |||
break; | |||
case CMD_MUTE: | |||
GLCD_PrintString("Mute"); | |||
usbHIDCode = MEDIA_MUTE; | |||
break; | |||
default: | |||
GLCD_PrintString("No command"); | |||
break; | |||
} | |||
// Mise à jour du rapport USB pour LUFA | |||
if (usbHIDCode != 0) { | |||
// Écriture dans la structure globale | |||
MediaReportData.CommandCode = usbHIDCode; | |||
} | |||
GLCD_Render(); | |||
} | |||
void send_general_command(GeneralCommand_t cmd) { | |||
GLCD_Clear(); | |||
GLCD_GotoXY(0, 20); | |||
uint8_t modifier = 0; | |||
uint8_t key = 0; | |||
switch (cmd) { | |||
case CMD_COPY: | |||
GLCD_PrintString("Copy"); | |||
modifier = HID_KEYBOARD_MODIFIER_LEFTCTRL; | |||
key = HID_KEYBOARD_SC_C; | |||
break; | |||
case CMD_PASTE: | |||
GLCD_PrintString("Paste"); | |||
modifier = HID_KEYBOARD_MODIFIER_LEFTCTRL; | |||
key = HID_KEYBOARD_SC_V; | |||
break; | |||
case CMD_SELECT_ALL: | |||
GLCD_PrintString("Select All"); | |||
modifier = HID_KEYBOARD_MODIFIER_LEFTCTRL; | |||
key = HID_KEYBOARD_SC_Q; // (A sur clavier AZERTY) | |||
break; | |||
case CMD_SEARCH: | |||
GLCD_PrintString("Search"); | |||
modifier = HID_KEYBOARD_MODIFIER_LEFTCTRL; | |||
key = HID_KEYBOARD_SC_F; | |||
break; | |||
default: | |||
GLCD_PrintString("No command"); | |||
break; | |||
} | |||
// Mise à jour de la structure globale pour Keyboard.c | |||
if (key != 0) { | |||
ShortcutReportData.Modifier = modifier; | |||
ShortcutReportData.KeyCode = key; | |||
} | |||
GLCD_Render(); | |||
} | |||
</syntaxhighlight> | |||
= Rendus (étudiants) = | = Rendus (étudiants) = | ||
Version actuelle datée du 27 janvier 2026 à 18:37
Proposition de système (étudiants)
L'idée de notre projet est d'avoir différents boutons programmables, qu'on puisse assigner à différentes actions tel que la gestion du volume : volume up, down, mute, ou encore, next track, previous track, etc...
On aura un écran LCD sur lequel il sera possible d'afficher le nouvel état (ex: nouveau volume ou nouveau média).
Contre-proposition (intervenant)
Le périphérique USB à émuler est un clavier. Vous pourrez simuler plus de touches que disponibles sur la carte électronique en utilisant des menus sur l'écran intégré à la carte.
Une bibliothèque pour gérer le contrôleur graphique SDD1306 est disponible [1].
Proposition définitive (étudiants)
Répartition du travail (étudiants)
Carte
Schéma initial (étudiants)
- schéma (projet KiCAD) : Fichier:I2L-2025-Carte-G1.zip
Carte routée (intervenant)
Vous utiliserez la carte avec l'écran OLED. Vous avez 4 touches et un joystick pour vos actions. La difficulté est d'utiliser l'écran OLED.
Composants (intervenant)
Au 9 octobre 2025, il manque un écran OLED.
Au 15 octobre 2025, l'écran OLED est disponible.
Carte réalisée (intervenant)
La carte est entiérement soudée. Eventuellement vous pouvez demander l'ajout d'un buzzer.
Travaux (étudiants)
- 13/10/2025 :
Programmation des boutons pour activer les différentes led.
Les boutons haut, bas, gauche et droite allument chacun une led. Chaque LED reste allumée tant que le bouton est appuyé.
Le bouton du milieu permet lui d'allumer les 4 LED, elles restent allumées tant qu'il est appuyé. Lorsque le bouton est laché, un delai d'une seconde est activé avant d'éteindre les LED.
+ début joystick
Pour avoir des valeurs de délai correctes, l'AVR utilise une fréquence de 8Mhz sans diviseur.
- 16/10/2025 :
Programmation du joystick (bouton et mouvement) pour allumer les LED et activer le buzzer. Les LED sont progressivement allumées et la fréquence du buzzer augmente en fonction de l'avancée du joystick.
Programmation de l'écran OLED. On affiche sur l'écran une barre de progression ainsi que la "vitesse" du joystick en fonction de son avancée.
- 13/11/2025 :
Création du menu sur l'écran OLED avec différents menus.
Les mouvements sur le joystick permettent de se déplacer dans les menus.
Le menu principal permet de sélectionner l'application sur laquelle on souhaite effectuer les actions (pour le moment Spotify uniquement). Ensuite un sous menu s'ouvre avec les actions disponibles sur l'application sélectionnée.
Pour le moment lors de la sélection d'une action, un simple texte s'affiche pendant 1 seconde avec le nom de l'action sélectionnée. Cela sera ensuite remplacée par une action envoyée au PC.
Fin de la séance :
Nous avons ajouté la possibilité de naviguer dans les menus avec les boutons.
Nous avons essayé d'ajouter LUFA sur le projet. Cependant, lorsque nous mettons le programme sur le controleur, celui ci n'est pas listé via la commande "lsusb".
- 27/01/2026 :
Nous avons ajouté LUFA sur le projet pour que le PC puisse détecter le micro controleur comme un Clavier et un controleur de média.
Nous avons actuellement un menu principal qui s'affiche sur l'écran avec 2 sous menu : Media et General
Le sous menu média propose les actions suivantes :
- Jouer/Mettre en pause le média actuel
- Passer au média suivant
- Revenir au média précédent
- Augmenter le volume du systeme
- Diminuer le volume du systeme
- Couper/Activer le son du systeme
Le sous menu général propose les actions suivantes :
- Copier (Ctrl + C)
- Coller (Ctrl + V)
- Tout sélectionner (Ctrl + A)
- Rechercher (Ctrl + F)
Pour les contrôles média, le micro controleur envoie des commandes en direct (Play/Pause, Volume up, ...) que le système d'exploitation interprète.
Pour les contrôles généraux, le micro controleur envoie des raccourcis clavier qui sont interprétés par le système d'exploitation comme si on utilisait la combinaison sur un clavier.
Pour la navigation dans les menus, celle-ci est possible grâce au joystick et/ou aux différents boutons :
Naviguer vers le haut : joystick vers le haut ou appui sur le bouton du haut
Naviguer vers le bas : joystick vers le bas ou appui sur le bouton du bas
Accéder au sous menu / effectuer l'action : joystick vers la droite ou appui sur le bouton du milieu ou appui sur le bouton de droite
Retour en arrière : joystick vers la gauche ou appui sur le bouton de gauche
Extraits significatifs de code (étudiants)
Gestion des mouvements du joystick et des appuis sur les boutons
int main(void)
{
SetupHardware();
// LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY);
GlobalInterruptEnable();
bool btn_bottom_pressed = false;
bool btn_top_pressed = false;
bool btn_left_pressed = false;
bool btn_right_pressed = false;
bool btn_mid_pressed = false;
// Joystick state flags to prevent spurious input at startup.
bool joystick_up_active = true;
bool joystick_down_active = true;
bool joystick_left_active = true;
bool joystick_right_active = true;
for (;;)
{
HID_Task();
USB_USBTask();
// --- Polling Buttons ---
// BTN_BOTTOM (Next)
if (button_is_pressed(BTN_BOTTOM)) {
if (!btn_bottom_pressed) {
menu_next();
btn_bottom_pressed = true;
}
} else {
btn_bottom_pressed = false;
}
// BTN_TOP (Prev)
if (button_is_pressed(BTN_TOP)) {
if (!btn_top_pressed) {
menu_prev();
btn_top_pressed = true;
}
} else {
btn_top_pressed = false;
}
// BTN_LEFT (Back)
if (button_is_pressed(BTN_LEFT)) {
if (!btn_left_pressed) {
menu_back();
btn_left_pressed = true;
}
} else {
btn_left_pressed = false;
}
// BTN_RIGHT (Select)
if (button_is_pressed(BTN_RIGHT)) {
if (!btn_right_pressed) {
menu_select();
btn_right_pressed = true;
}
} else {
btn_right_pressed = false;
}
// BTN_MID (Select)
if (button_is_pressed(BTN_MID)) {
if (!btn_mid_pressed) {
menu_select();
btn_mid_pressed = true;
}
} else {
btn_mid_pressed = false;
}
// --- User Joystick Logic ---
uint16_t x = joystick_read_x();
uint16_t y = joystick_read_y();
// Move down
if (y < JOY_DOWN_THRESHOLD && !joystick_down_active) {
menu_next();
joystick_down_active = true;
} else if (y >= JOY_CENTER_MIN) {
joystick_down_active = false;
}
// Move up
if (y > JOY_UP_THRESHOLD && !joystick_up_active) {
menu_prev();
joystick_up_active = true;
} else if (y <= JOY_CENTER_MAX) {
joystick_up_active = false;
}
// Select item
if (x < JOY_RIGHT_THRESHOLD && !joystick_right_active) {
menu_select();
joystick_right_active = true;
} else if (x >= JOY_CENTER_MIN) {
joystick_right_active = false;
}
// Go back
if (x > JOY_LEFT_THRESHOLD && !joystick_left_active) {
menu_back();
joystick_left_active = true;
} else if (x <= JOY_CENTER_MAX) {
joystick_left_active = false;
}
// --- Display ---
display_current_menu();
}
}
Lecture de l'état d'un bouton
#include "button.h"
void button_init(void) {
// LEFT, RIGHT, TOP sur PORTB
BTN_LRT_DDR &= ~((1 << BTN_LEFT_PIN) | (1 << BTN_RIGHT_PIN) | (1 << BTN_TOP_PIN));
BTN_LRT_PORT |= (1 << BTN_LEFT_PIN) | (1 << BTN_RIGHT_PIN) | (1 << BTN_TOP_PIN);
// BOTTOM sur PORTC
BTN_BOTTOM_DDR &= ~(1 << BTN_BOTTOM_PIN);
BTN_BOTTOM_PORT |= (1 << BTN_BOTTOM_PIN);
// MID sur PORTE
BTN_MID_DDR &= ~(1 << BTN_MID_PIN);
BTN_MID_PORT |= (1 << BTN_MID_PIN);
}
uint8_t button_is_pressed(button_t btn) {
switch (btn) {
case BTN_LEFT:
return !(BTN_LRT_PIN_REG & (1 << BTN_LEFT_PIN));
case BTN_RIGHT:
return !(BTN_LRT_PIN_REG & (1 << BTN_RIGHT_PIN));
case BTN_TOP:
return !(BTN_LRT_PIN_REG & (1 << BTN_TOP_PIN));
case BTN_BOTTOM:
return !(BTN_BOTTOM_PIN_REG & (1 << BTN_BOTTOM_PIN));
case BTN_MID:
return !(BTN_MID_PIN_REG & (1 << BTN_MID_PIN));
default:
return 0;
}
}
Lecture de l'état du joystick
#include "joystick.h"
void joystick_init(void) {
// === Configurer les broches du joystick en entrée ===
JOYSTICK_DDR &= ~((1 << JOYSTICK_X) | (1 << JOYSTICK_Y) | (1 << JOYSTICK_BTN));
// Activer la résistance de pull-up pour le bouton du joystick
JOYSTICK_PORT |= (1 << JOYSTICK_BTN);
// === Initialisation de l'ADC ===
ADMUX = (1 << REFS0); // Référence de tension = AVcc
ADCSRA = (1 << ADEN) // Activer ADC
| (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // Prescaler = 128
}
static uint16_t adc_read(uint8_t channel) {
ADMUX = (ADMUX & 0xF0) | (channel & 0x0F); // Sélection du canal (PF4 = ADC4, PF5 = ADC5)
ADCSRA |= (1 << ADSC); // Lancer la conversion
while (ADCSRA & (1 << ADSC)); // Attendre la fin
return ADC; // Retourner la valeur sur 10 bits (0–1023)
}
uint16_t joystick_read_x(void) {
return adc_read(JOYSTICK_X); // PF4 → ADC4
}
uint16_t joystick_read_y(void) {
return adc_read(JOYSTICK_Y); // PF5 → ADC5
}
uint8_t joystick_is_pressed(void) {
return !(JOYSTICK_PIN_REG & (1 << JOYSTICK_BTN)); // Bouton actif à l'état bas
}
Affichage, Navigation et actions dans les menus
static Menu* current_menu;
// --- Private Functions ---
static void display_action_feedback(const char* message) {
GLCD_Clear();
GLCD_GotoXY(10, 28);
GLCD_PrintString((char*)message);
GLCD_Render();
all_led_on();
buzzer_on();
_delay_ms(200);
buzzer_off();
all_led_off();
}
// --- Public API Implementation ---
void menu_init(void) {
current_menu = &main_menu;
}
void display_current_menu(void) {
GLCD_Clear();
GLCD_GotoXY(2, 2);
GLCD_PrintString((char*)current_menu->title);
const uint8_t FONT_HEIGHT = 8;
const uint8_t ITEM_PADDING = 3;
const uint8_t rect_height = FONT_HEIGHT + ITEM_PADDING * 2;
const uint8_t max_visible_items = 3;
for (uint8_t i = 0; i < max_visible_items; i++) {
uint8_t item_index = current_menu->scroll_offset + i;
if (item_index >= current_menu->num_items) break;
uint8_t y_position = 15 + i * rect_height;
if (item_index == current_menu->selected_item) {
GLCD_DrawRectangle(1, y_position, 126, y_position + rect_height - 1, GLCD_Black);
GLCD_GotoXY(5, y_position + ITEM_PADDING);
GLCD_PrintString((char*)current_menu->items[item_index].name);
} else {
GLCD_GotoXY(5, y_position + ITEM_PADDING);
GLCD_PrintString((char*)current_menu->items[item_index].name);
}
}
GLCD_Render();
}
void menu_next(void) {
if (current_menu->selected_item < current_menu->num_items - 1) {
current_menu->selected_item++;
if (current_menu->selected_item >= current_menu->scroll_offset + 3) {
current_menu->scroll_offset++;
}
}
}
void menu_prev(void) {
if (current_menu->selected_item > 0) {
current_menu->selected_item--;
if (current_menu->selected_item < current_menu->scroll_offset) {
current_menu->scroll_offset = current_menu->selected_item;
}
}
}
void menu_select(void) {
const MenuItem* selected = ¤t_menu->items[current_menu->selected_item];
if (selected->type == MENU_ITEM_TYPE_SUBMENU && selected->target.submenu != NULL) {
current_menu = selected->target.submenu;
} else if (selected->type == MENU_ITEM_TYPE_ACTION && selected->target.action != NULL) {
selected->target.action();
display_action_feedback(selected->name);
}
}
void menu_back(void) {
if (current_menu->parent != NULL) {
current_menu = current_menu->parent;
}
}
Déclaration des menus
// --- Action Prototypes ---
void action_media_play_pause(void) { send_media_command(CMD_PLAY_PAUSE); }
void action_media_next(void) { send_media_command(CMD_NEXT_TRACK); }
void action_media_prev(void) { send_media_command(CMD_PREV_TRACK); }
void action_media_vol_up(void) { send_media_command(CMD_VOLUME_UP); }
void action_media_vol_down(void) { send_media_command(CMD_VOLUME_DOWN); }
void action_media_mute(void) { send_media_command(CMD_MUTE); }
void action_general_copy(void) { send_general_command(CMD_COPY); }
void action_general_paste(void) { send_general_command(CMD_PASTE); }
void action_general_select_all(void) { send_general_command(CMD_SELECT_ALL); }
void action_general_search(void) { send_general_command(CMD_SEARCH); }
// --- Menu Definitions ---
// Forward declare menus
extern Menu main_menu;
extern Menu media_menu;
extern Menu settings_menu;
extern Menu code_menu;
// --- General Menu ---
const MenuItem general_menu_items[] = {
{"Copy", MENU_ITEM_TYPE_ACTION, .target.action = &action_general_copy},
{"Paste", MENU_ITEM_TYPE_ACTION, .target.action = &action_general_paste},
{"Select all", MENU_ITEM_TYPE_ACTION, .target.action = &action_general_select_all},
{"Search", MENU_ITEM_TYPE_ACTION, .target.action = &action_general_search},
};
Menu general_menu = {
.title = "General",
.items = general_menu_items,
.num_items = sizeof(general_menu_items) / sizeof(MenuItem),
.selected_item = 0,
.scroll_offset = 0,
.parent = &main_menu
};
// --- Media Menu ---
const MenuItem media_menu_items[] = {
{"Play/Pause", MENU_ITEM_TYPE_ACTION, .target.action = &action_media_play_pause},
{"Next Track", MENU_ITEM_TYPE_ACTION, .target.action = &action_media_next},
{"Prev Track", MENU_ITEM_TYPE_ACTION, .target.action = &action_media_prev},
{"Volume +", MENU_ITEM_TYPE_ACTION, .target.action = &action_media_vol_up},
{"Volume -", MENU_ITEM_TYPE_ACTION, .target.action = &action_media_vol_down},
{"Mute", MENU_ITEM_TYPE_ACTION, .target.action = &action_media_mute},
};
Menu media_menu = {
.title = "Media",
.items = media_menu_items,
.num_items = sizeof(media_menu_items) / sizeof(MenuItem),
.selected_item = 0,
.scroll_offset = 0,
.parent = &main_menu
};
// --- Main Menu ---
const MenuItem main_menu_items[] = {
{"Media", MENU_ITEM_TYPE_SUBMENU, .target.submenu = &media_menu},
{"General", MENU_ITEM_TYPE_SUBMENU, .target.submenu = &general_menu},
};
Menu main_menu = {
.title = "Main Menu",
.items = main_menu_items,
.num_items = sizeof(main_menu_items) / sizeof(MenuItem),
.selected_item = 0,
.scroll_offset = 0,
.parent = NULL
};
Gestion et envoi des actions au PC
#include "actions.h"
#include <avr/io.h>
#include "../Keyboard.h"
// Codes standards USB HID Consumer
#define MEDIA_PLAY_PAUSE 0xCD
#define MEDIA_NEXT_TRACK 0xB5
#define MEDIA_PREV_TRACK 0xB6
#define MEDIA_VOLUME_UP 0xE9
#define MEDIA_VOLUME_DOWN 0xEA
#define MEDIA_MUTE 0xE2
void send_media_command(MediaCommand_t cmd) {
GLCD_Clear();
GLCD_GotoXY(0, 20);
uint16_t usbHIDCode = 0;
switch (cmd) {
case CMD_PLAY_PAUSE:
GLCD_PrintString("Play/Pause");
usbHIDCode = MEDIA_PLAY_PAUSE;
break;
case CMD_NEXT_TRACK:
GLCD_PrintString("Next Track");
usbHIDCode = MEDIA_NEXT_TRACK;
break;
case CMD_PREV_TRACK:
GLCD_PrintString("Prev Track");
usbHIDCode = MEDIA_PREV_TRACK;
break;
case CMD_VOLUME_UP:
GLCD_PrintString("Volume Up");
usbHIDCode = MEDIA_VOLUME_UP;
break;
case CMD_VOLUME_DOWN:
GLCD_PrintString("Volume Down");
usbHIDCode = MEDIA_VOLUME_DOWN;
break;
case CMD_MUTE:
GLCD_PrintString("Mute");
usbHIDCode = MEDIA_MUTE;
break;
default:
GLCD_PrintString("No command");
break;
}
// Mise à jour du rapport USB pour LUFA
if (usbHIDCode != 0) {
// Écriture dans la structure globale
MediaReportData.CommandCode = usbHIDCode;
}
GLCD_Render();
}
void send_general_command(GeneralCommand_t cmd) {
GLCD_Clear();
GLCD_GotoXY(0, 20);
uint8_t modifier = 0;
uint8_t key = 0;
switch (cmd) {
case CMD_COPY:
GLCD_PrintString("Copy");
modifier = HID_KEYBOARD_MODIFIER_LEFTCTRL;
key = HID_KEYBOARD_SC_C;
break;
case CMD_PASTE:
GLCD_PrintString("Paste");
modifier = HID_KEYBOARD_MODIFIER_LEFTCTRL;
key = HID_KEYBOARD_SC_V;
break;
case CMD_SELECT_ALL:
GLCD_PrintString("Select All");
modifier = HID_KEYBOARD_MODIFIER_LEFTCTRL;
key = HID_KEYBOARD_SC_Q; // (A sur clavier AZERTY)
break;
case CMD_SEARCH:
GLCD_PrintString("Search");
modifier = HID_KEYBOARD_MODIFIER_LEFTCTRL;
key = HID_KEYBOARD_SC_F;
break;
default:
GLCD_PrintString("No command");
break;
}
// Mise à jour de la structure globale pour Keyboard.c
if (key != 0) {
ShortcutReportData.Modifier = modifier;
ShortcutReportData.KeyCode = key;
}
GLCD_Render();
}
Rendus (étudiants)
Projet KiCAD : Fichier:I2L-2025-Carte-G1-final.zip
Programmes :
- microcontrôleur : Fichier:I2L-2025-Programmes-uC-G1.zip
- ordinateur Fichier:I2L-2025-Programmes-PC-G1.zip