I2L 2025 Groupe1

De wiki-se.plil.fr
Révision datée du 27 janvier 2026 à 18:37 par Tobein (discussion | contributions) (→‎Travaux (étudiants))
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à la navigation Aller à la recherche

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 de la carte

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)

Photo de la carte

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 = &current_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 :