« I2L 2025 Groupe8 » : différence entre les versions

De wiki-se.plil.fr
Aller à la navigation Aller à la recherche
 
(24 versions intermédiaires par 2 utilisateurs non affichées)
Ligne 90 : Ligne 90 :


Pour le badge (bleu), on a constaté qu'il faudrait avoir une certaine distance pour que ça fonctionne, et le blanc (petit rectangle) il faut coller directement sur le lecteur NFC.  
Pour le badge (bleu), on a constaté qu'il faudrait avoir une certaine distance pour que ça fonctionne, et le blanc (petit rectangle) il faut coller directement sur le lecteur NFC.  
=== '''Rendu Final''' ===
==== Description du Projet ====
L'objectif de ce projet est de réaliser un système d'authentification physique pour une application Web via la technologie NFC. Le système repose sur une architecture matérielle composée d'un microcontrôleur ATmega32u4 et d'un contrôleur NFC PN532. La partie logicielle comprend une application Web de démonstration et une API Python (faisant office de passerelle) pour assurer la communication série (USB) entre le matériel et l'interface Web.
Le système propose deux modes de fonctionnement :
# '''Mode autonome''' : Ce mode permet d'utiliser le lecteur NFC de manière indépendante. Grâce à un menu interactif sur l'écran OLED, l'utilisateur peut lire et visualiser les informations des badges (UID, nom, prénom) directement sur le boîtier, sans nécessiter de connexion à un ordinateur.
# '''Mode connecté (USB) :''' Une fois le module connecté à l'ordinateur, ce mode active la communication avec l'API Python. Il permet trois actions principales :
#* Lecture étendue : Transmission des données du badge scanné vers l'ordinateur.
#* Authentification Web : Déverrouillage de l'application Web par simple scan d'un badge enregistré.
#* Écriture : Enregistrement de nouveaux utilisateurs en écrivant les données (nom, prénom, mot de passe) sur une carte vierge depuis l'interface.
Matériel utilisé :
[[Fichier:Microcontroller.png|centré|vignette|Carte complete intégrant le lecteur NFC]]
[[Fichier:Badge NFC.png|centré|vignette|Badge NFC]]
[[Fichier:Badge NFC 2.png|centré|vignette]]
PS: Pour le badge (bleu), on a constaté qu'il faudrait avoir une certaine distance pour que ça fonctionne, et le blanc (petit rectangle) il faut coller directement sur le lecteur NFC.
La réalisation de ce projet nécessitait l'interaction avec deux périphériques principaux : le lecteur NFC et l'écran OLED SSD1306. La plupart des solutions open-source existantes (comme ssd1306 ou pn532-lib) étant conçues spécifiquement pour l'environnement Arduino, elles n'étaient pas directement compatibles avec notre architecture logicielle sur l'ATmega32u4.
Nous avons donc développé nos propres bibliothèques en nous inspirant de l’existant :
# Communication I2C : Nous avons d'abord écrit une bibliothèque bas niveau pour gérer le protocole I2C sur le microcontrôleur. C'est la fondation utilisée par nos autres pilotes.
#* Dépôt : i2c-lib-atmega32u4 https://github.com/sedar007/i2c-lib-atmega32u4.git
# Gestion de l'écran OLED : En nous basant sur la bibliothèque de lexus2k, nous avons réécrit un pilote optimisé et compatible avec notre couche I2C.
#* Dépôt : ssd1306-avr https://github.com/sedar007/ssd1306-avr.git
# Gestion du lecteur NFC : De même, pour le PN532, nous avons adapté les bibliothèques Arduino existantes pour créer une version autonome utilisant notre pilote I2C.
#* Dépôt : pn532-lib-atmega32u4 https://github.com/sedar007/pn532-lib-atmega32u4.git


= Extraits significatifs de code (étudiants) =
= Extraits significatifs de code (étudiants) =
<big>'''1. Mode autonome'''</big>
'''<big>1. Badge NFC</big>'''
 
Les données d’un badge NFC sont stockées dans des '''blocs mémoire bien définis''', chacun étant identifié par une '''adresse numérique'''.
 
Le badge est donc découpé en plusieurs blocs, et chaque bloc peut contenir une information spécifique.
 
Dans notre cas, nous avons choisi d’organiser les données de la manière suivante : le '''nom''' est stocké dans le '''bloc 1''', le '''prénom''' dans le '''bloc 2''', et le '''mot de passe''' dans le '''bloc 5''', comme indiqué ci-dessous.<syntaxhighlight lang="c">
// Configuration Blocs NFC
#define BLOCK_ADDR_NAME    1
#define BLOCK_ADDR_PRENOM  2
#define BLOCK_ADDR_PASS    5
</syntaxhighlight>Cette fonction ci dessous écrit des données dans un bloc spécifique d’un badge NFC après avoir authentifié l’accès. Elle complète le bloc à 16 octets avec des zéros si nécessaire et retourne <code>true</code> si l’écriture réussit, sinon <code>false</code>.<syntaxhighlight lang="c">
bool helper_write_block(PN532 *pn532, uint8_t block_addr, uint8_t *uid, int32_t uid_len,
                              uint8_t *data, const char *label) {
    // Préparation du bloc de 16 octets
    // Remplissage 0 si data plus court
    uint8_t block_data[BLOCK_SIZE];
    memset(block_data, 0, BLOCK_SIZE);
   
    size_t len = strlen((char*)data);
    if (len > BLOCK_SIZE) len = BLOCK_SIZE;
    memcpy(block_data, data, len);
 
    if (PN532_MifareClassicAuthenticateBlock(pn532, uid, uid_len, block_addr, MIFARE_CMD_AUTH_A, KEY_DEFAULT) != PN532_ERROR_NONE)
        return false;
 
    return (PN532_MifareClassicWriteBlock(pn532, block_data, block_addr) == PN532_ERROR_NONE);
}
</syntaxhighlight>Voici la méthode permettant de lire un bloc et d'afficher son contenu à l'écran : <syntaxhighlight lang="c">
// Lit un bloc. Si dest_buffer != NULL, copie les données brutes.
bool helper_read_block(PN532 *pn532, uint8_t block_addr, uint8_t *uid, int32_t uid_len,
                              uint8_t *dest_buffer, const char *label, uint8_t ui_line) {
    uint8_t raw_data[BLOCK_SIZE];
    char display_str[32];
    char safe_content[BLOCK_SIZE + 1];
 
    // Authentification + Lecture
    if (PN532_MifareClassicAuthenticateBlock(pn532, uid, uid_len, block_addr, MIFARE_CMD_AUTH_A, KEY_DEFAULT) == PN532_ERROR_NONE &&
        PN532_MifareClassicReadBlock(pn532, raw_data, block_addr) == PN532_ERROR_NONE)
    {
        // Copie vers destination
        if (dest_buffer != NULL)
            memcpy(dest_buffer, raw_data, BLOCK_SIZE);
 
        // Nettoyage pour affichage
        memcpy(safe_content, raw_data, BLOCK_SIZE);
        safe_content[BLOCK_SIZE] = '\0';
        for (int i = 0; i < BLOCK_SIZE; i++)
            if (!isprint((unsigned char)safe_content[i])) safe_content[i] = ' ';
 
        snprintf(display_str, sizeof(display_str), "%s: %s", label, safe_content);
        ssd1306_print_utf8_center(display_str, ui_line);
        return true;
    }
    else {
        snprintf(display_str, sizeof(display_str), "%s: Erreur", label);
        ssd1306_print_utf8_center(display_str, ui_line);
        return false;
    }
}
</syntaxhighlight>
 
<big><br />'''2. Mode autonome'''</big>
Pour le mode autonome sans PC, on a créée cette fonction ci-dessous. Elle '''attend qu’une carte NFC soit présentée''', lit ses données principales (nom, prénom), les affiche sur l’écran OLED.<syntaxhighlight lang="c">
 
void action_infos(void) {
    uint8_t uid[MIFARE_UID_MAX_LENGTH];
   
    ssd1306_clear();
    ui_wait_card_screen("pour infos...");
    PORTD |= (1 << LED6);
 
    int32_t uid_len = detect_card(&pn532, uid);
    if (uid_len <= 0) {
        ui_show_msg("Pas de carte", 3);
        _delay_ms(DELAY_MSG_SHORT);
        goto cleanup;
    }
 
    ssd1306_clear();
 
    ui_format_and_print_uid(uid, uid_len);
 
    helper_read_block(&pn532,BLOCK_ADDR_NAME,  uid, uid_len, NULL, "Nom",    3);
    helper_read_block(&pn532,BLOCK_ADDR_PRENOM, uid, uid_len, NULL, "Prenom", 5);
 
    _delay_ms(SELECTION_DELAY);
 
cleanup:
    PORTD &= ~(1 << LED6);
    ssd1306_clear();
}
</syntaxhighlight>
 
 
 
'''<big>3. Mode connecté USB</big>'''
 
On propose aussi un mode connecté USB sur PC pour pouvoir gérer les informations présentes sur le badge.
 
Cette fonction permet de lire les informations présentes sur le badge NFC et de l'envoyer au PC pour être afficher à l'interface utilisateur : <syntaxhighlight lang="c">
void action_read(void) {
    uint8_t uid[MIFARE_UID_MAX_LENGTH];
    uint8_t usb_buffer[USB_BUF_SIZE] = {0};
   
    ssd1306_clear();
    ui_wait_card_screen("pour lire...");
    PORTD |= (1 << LED5);
 
    int32_t uid_len = detect_card(&pn532, uid);
    if (uid_len <= 0) {
        ui_show_msg("Pas de carte", 3);
        _delay_ms(DELAY_MSG_LONG);
        goto cleanup;
    }
 
    ssd1306_clear();
 
    usb_buffer[0] = 'R';
    int copy_len = (uid_len > 7) ? 7 : uid_len;
    memcpy(&usb_buffer[OFFSET_UID], uid, copy_len);
 
    ui_format_and_print_uid(uid, uid_len);
   
    helper_read_block(&pn532, BLOCK_ADDR_NAME,  uid, uid_len, &usb_buffer[OFFSET_NAME],  "Nom",    3);
    helper_read_block(&pn532,BLOCK_ADDR_PRENOM, uid, uid_len, &usb_buffer[OFFSET_PRENOM], "Prenom", 5);
    helper_read_block(&pn532,BLOCK_ADDR_PASS,  uid, uid_len, &usb_buffer[OFFSET_PASS],  "Pass",  7);
 
    usb_send_response(usb_buffer, USB_BUF_SIZE);
   
    _delay_ms(SELECTION_DELAY * 2);
 
cleanup:
    PORTD &= ~(1 << LED5);
    ssd1306_clear();
}
 
</syntaxhighlight>Voici la fonction pour envoyer les informations au PC :<syntaxhighlight lang="c">
void usb_send_response(uint8_t *buffer, uint16_t size) {
    Endpoint_SelectEndpoint(MYIN_EPADDR);
    if (Endpoint_IsINReady()) {
        Endpoint_Write_Stream_LE(buffer, size, NULL);
        Endpoint_ClearIN();
    }
}
 
</syntaxhighlight>Cette méthode ci-dessous permet d'écrire sur le badge NFC via le PC :<syntaxhighlight lang="c">
void action_write(void) {
    uint8_t uid[MIFARE_UID_MAX_LENGTH];
    uint8_t usb_buffer[USB_BUF_SIZE];
   
    ssd1306_clear();
    ssd1306_print_utf8_center("En attente", 2);
    ssd1306_print_utf8_center("donnees USB...", 4);
    PORTD |= (1 << LED4);
 
    bool received = false;
    for (int i = 0; i < TIMEOUT_USB_WAIT; i++) {
        USB_USBTask();
        Endpoint_SelectEndpoint(MYOUT_EPADDR);
        if (Endpoint_IsOUTReceived()) {
            if (Endpoint_BytesInEndpoint() >= 52) { // Header(4) + Data(48)
                Endpoint_Read_Stream_LE(usb_buffer, USB_BUF_SIZE, NULL);
                Endpoint_ClearOUT();
                received = true;
                break;
            }
        }
        _delay_ms(50);
    }
 
    if (!received) {
        ui_show_msg("Timeout USB", 3);
        _delay_ms(DELAY_MSG_SHORT);
        goto cleanup;
    }
 
    if (usb_buffer[0] != 'W') {
        ui_show_msg("Cmd Invalide", 3);
        _delay_ms(DELAY_MSG_SHORT);
        goto cleanup;
    }
 
    uint8_t len_n = usb_buffer[1];
    uint8_t len_p = usb_buffer[2];
    uint8_t len_pw = usb_buffer[3];
 
    // Buffers à écrire max 16 octets + null
    uint8_t data_name[17] = {0};
    uint8_t data_prenom[17] = {0};
    uint8_t data_pass[17] = {0};
 
    memcpy(data_name,  &usb_buffer[4],      (len_n > 16) ? 16 : len_n);
    memcpy(data_prenom, &usb_buffer[4 + 16], (len_p > 16) ? 16 : len_p);
    memcpy(data_pass,  &usb_buffer[4 + 32], (len_pw > 16) ? 16 : len_pw);
 
    ssd1306_clear();
    char tmp_disp[32];
    snprintf(tmp_disp, 32, "Nom: %s", data_name);
    ssd1306_print_utf8_center(tmp_disp, 1);
    snprintf(tmp_disp, 32, "Pre: %s", data_prenom);
    ssd1306_print_utf8_center(tmp_disp, 3);
    snprintf(tmp_disp, 32, "Pas: %s", data_pass);
    ssd1306_print_utf8_center(tmp_disp, 5);
    ssd1306_print_utf8_center("Approchez carte", 7);
   
    _delay_ms(DELAY_MSG_SHORT);
 
    int32_t uid_len = detect_card(&pn532, uid);
    if (uid_len <= 0) {
        ui_show_msg("Pas de carte", 3);
        _delay_ms(DELAY_MSG_SHORT);
        goto cleanup;
    }
 
    ui_show_msg("Ecriture...", 3);
   
    bool success = true;
    if (success) success = helper_write_block(&pn532, BLOCK_ADDR_NAME,  uid, uid_len, data_name,  "Nom");
    _delay_ms(20);
    if (success) success = helper_write_block(&pn532,BLOCK_ADDR_PRENOM, uid, uid_len, data_prenom, "Prenom");
    _delay_ms(20);
    if (success) success = helper_write_block(&pn532,BLOCK_ADDR_PASS,  uid, uid_len, data_pass,  "Pass");
 
    uint8_t response[64] = {0};
    response[0] = 'W';
    response[1] = success ? 0x01 : 0x00;
    usb_send_response(response, 64);
 
    ui_show_msg(success ? "Ecriture OK!" : "Echec Ecriture", 3);
    _delay_ms(DELAY_MSG_LONG);
 
cleanup:
    PORTD &= ~(1 << LED4);
    ssd1306_clear();
}
</syntaxhighlight>
 
Le code Python ci-dessous permet de communiquer en USB avec le microcontrôleur et sert d’API pour l'application Web
 
Tout d’abord, on récupère les informations de LUFA :<syntaxhighlight lang="python3">
VENDOR_ID = 0x4242
PRODUCT_ID = 0x0001
EP_IN = 0x81
EP_OUT = 0x02
EP_SIZE = 64
TIMEOUT = 1000
</syntaxhighlight>
Cette fonction assure la gestion de la communication USB.<syntaxhighlight lang="python3">
def monitor_usb():
    global current_card_info, usb_handle, usb_context
 
    if usb_context is None or usb_handle is None:
        print("Aucun périphérique USB initialisé, arrêt du monitoring.")
        return
 
    print("=== Monitoring USB actif ===\n")
 
    while True:
        try:
            data = usb_handle.interruptRead(EP_IN, EP_SIZE, timeout=TIMEOUT)
            if len(data) > 0:
                print(f"\n[{int(time.time())}] Reçu {len(data)} octets:")
                print("="*60)
                for i in range(0, len(data), 16):
                    hex_part = ' '.join(f'{b:02X}' for b in data[i:i+16])
                    ascii_part = ''.join(chr(b) if 32 <= b < 127 else '.' for b in data[i:i+16])
                    print(f"{i:04d}: {hex_part:<48} | {ascii_part}")
                print("="*60)
 
                card_info = parse_card_data(data)
                if card_info:
                    current_card_info = card_info
                    print("\n=== DONNÉES PARSÉES ===")
                    for k, v in card_info.items():
                        print(f"{k.capitalize():<10}: {v}")
                    print("="*60 + "\n")
 
        except usb1.USBErrorTimeout:
            pass
        except usb1.USBErrorNoDevice:
            print("\nPériphérique déconnecté.")
            usb_handle = None
            break
        except Exception as e:
            print(f"\nErreur inattendue: {e}")
            import traceback
            traceback.print_exc()
            break
 
        time.sleep(0.05)
 
</syntaxhighlight>
Cette fonction assure le traitement des données USB entrantes avant de les envoyer vers l'interface Web.<syntaxhighlight lang="python">
def parse_card_data(data):
    if len(data) < 56:
        print(f"Données trop courtes: {len(data)} octets")
        return None
    if data[0] != ord('R'):
        print(f"Type incorrect: {chr(data[0]) if data[0] < 127 else data[0]}")
        return None
 
    uid_bytes = [b for b in data[1:8] if b != 0]
    uid = ''.join(f'{b:02X}' for b in uid_bytes)
 
    nom = bytes(data[8:24]).decode('ascii', errors='ignore').strip('\x00 ')
    prenom = bytes(data[24:40]).decode('ascii', errors='ignore').strip('\x00 ')
    password = bytes(data[40:56]).decode('ascii', errors='ignore').strip('\x00 ')
 
    return {'uid': uid, 'nom': nom, 'prenom': prenom, 'password': password}
 
</syntaxhighlight>
Cette méthode permet d’envoyer les données reçues de l’API vers le périphérique USB<syntaxhighlight lang="python">
 
def write_to_badge(nom, prenom, password):
    global usb_handle
 
    if usb_handle is None:
        return {'success': False, 'error': 'Périphérique USB non connecté'}
 
    try:
        nom_bytes = nom.encode('ascii')[:16]
        prenom_bytes = prenom.encode('ascii')[:16]
        password_bytes = password.encode('ascii')[:16]


En mode autonome, le microcontrôleur permet de '''lire, écrire et afficher''' les informations présentes sur le badge.
        packet = bytearray(64)
        packet[0] = ord('W')
        packet[1] = len(nom_bytes)
        packet[2] = len(prenom_bytes)
        packet[3] = len(password_bytes)
        packet[4:4+len(nom_bytes)] = nom_bytes
        packet[20:20+len(prenom_bytes)] = prenom_bytes
        packet[36:36+len(password_bytes)] = password_bytes


==== a) Librairie SSD1306 ====
        print("\n=== ENVOI DONNÉES BADGE ===")
Après avoir testé plusieurs codes et bibliothèques existantes (dont le fonctionnement n’était pas satisfaisant), nous avons finalement choisi de '''créer et d’adapter notre propre version''' à partir des différentes implémentations étudiées.
        print(f"Nom: {nom} | Prénom: {prenom} | Pass: {password}")
        usb_handle.interruptWrite(EP_OUT, bytes(packet), timeout=5000)
        print("En attente de la réponse...")


Cette nouvelle version de la librairie SSD1306 permet :
        try:
            response = usb_handle.interruptRead(EP_IN, EP_SIZE, timeout=10000)
            if len(response) > 0 and response[0] == ord('W'):
                success = response[1] == 0x01
                print(f"Réponse reçue: {'OK' if success else 'ERREUR'}")
                return {'success': success, 'message': 'Écriture réussie' if success else 'Erreur lors de l\'écriture'}
            return {'success': False, 'error': 'Réponse invalide'}


* un '''meilleur fonctionnement de l’écran OLED''',
        except usb1.USBErrorTimeout:
* une '''affichage plus lisible et plus fluide''',
            return {'success': False, 'error': 'Timeout - Pas de réponse du badge'}
* une '''meilleure maîtrise des fonctions utilisées''', adaptées à notre matériel et à nos besoins.


Ci-dessous figurent les '''en-têtes des méthodes utilisées''' dans cette librairie améliorée :<syntaxhighlight lang="c" line="1">
    except Exception as e:
#ifndef SSD1306_AVR_SSD1306_H
        return {'success': False, 'error': str(e)}
#define SSD1306_AVR_SSD1306_H


#pragma once
#include <stdint.h>
#include <stddef.h>


#define SSD1306_I2C_ADDR 0x3C
</syntaxhighlight>
#define SSD1306_CTRL_CMD 0x00
L'API développée en Python<syntaxhighlight lang="python">
#define SSD1306_CTRL_DATA 0x40
app.route('/api/nfc/status')
def nfc_status():
    return jsonify({
        'usb_connected': usb_handle is not None,
        'card_detected': current_card_info is not None,
        'info': current_card_info
    })


#define SSD1306_WIDTH 128
@app.route('/api/nfc/info')
#define SSD1306_HEIGHT 64
def get_current_info():
#define SSD1306_PAGES (SSD1306_HEIGHT/8)
    if current_card_info:
        return jsonify(current_card_info)
    return jsonify({'error': 'Aucune carte scannée'}), 404


void ssd1306_init(void);
@app.route('/api/nfc/register', methods=['POST'])
void ssd1306_send_command(uint8_t cmd);
def write_nfc():
void ssd1306_send_data_byte(uint8_t data);
    global usb_handle
void ssd1306_send_data(const uint8_t *buf, size_t len);
    if usb_handle is None:
void ssd1306_set_cursor(uint8_t page, uint8_t col); // page: 0..7, col: 0..127
        return jsonify({'error': 'Périphérique USB non connecté', 'success': False}), 503
void ssd1306_clear(void);
void ssd1306_clear_page(uint8_t page);


#endif //SSD1306_AVR_SSD1306_H
    data = request.get_json()
</syntaxhighlight>Voici le code permettant de bien positionner le bloc de texte : <syntaxhighlight lang="c" line="1">
    if not data:
#ifndef SSD1306_AVR_SSD1306_TEXT_H
        return jsonify({'error': 'Corps JSON manquant', 'success': False}), 400
#define SSD1306_AVR_SSD1306_TEXT_H
#pragma once
#include <stdint.h>


// Print UTF-8 string with optional max width and optional scroll offset
    nom = data.get('nom', '').strip()
void ssd1306_print_utf8_with_max_width_scroll(const char *s, uint8_t start_page, uint8_t start_col,
    prenom = data.get('prenom', '').strip()
                                              uint8_t max_width_px, uint16_t scroll_px);
    password = data.get('password', '').strip()


// Print UTF-8 string across the full width
    if not nom or not prenom or not password:
void ssd1306_print_utf8(const char *s, uint8_t start_page, uint8_t start_col);
        return jsonify({'error': 'nom, prenom et password sont requis', 'success': False}), 400


// Print UTF-8 string with optional max width
    if len(nom) > 16 or len(prenom) > 16 or len(password) > 16:
void ssd1306_print_utf8_with_max_width(const char *s, uint8_t start_page, uint8_t start_col, uint8_t max_width_px);
        return jsonify({'error': 'Champs trop longs (max 16 caractères)', 'success': False}), 400


// Print UTF-8 string centered on the page
    print(f"\n=== REQUÊTE D'ÉCRITURE BADGE ===\nNom: {nom}, Prénom: {prenom}, Password: {password}")
void ssd1306_print_utf8_center(const char *s, uint8_t page) ;
    result = write_to_badge(nom, prenom, password)
// Print a single character at a specific page/column
    return jsonify(result), (200 if result['success'] else 500)
void ssd1306_putc(char c, uint8_t page, uint8_t col);


void ssd1306_print_utf8_scroll(const char *s, uint8_t page, uint8_t start_col,
</syntaxhighlight>
                              uint8_t max_width_px, uint16_t scroll_offset);
uint16_t measure_string_px(const char *s);


#endif //SSD1306_AVR_SSD1306_TEXT_H
Ce code sert à démarrer le programme en lançant deux tâches simultanées. D'abord, il active le serveur Web en arrière-plan pour qu'il ne bloque pas le reste du script. Ensuite, il connecte le microcontrôleur en USB (en s'assurant que l'ordinateur lui laisse l'accès exclusif) et lance la boucle de surveillance pour détecter et traiter les badges en temps réel.<syntaxhighlight lang="python">
</syntaxhighlight>Voici le code permettant de formater les caractères affichés à l'écran : <syntaxhighlight lang="c" line="1">
if __name__ == '__main__':
#ifndef SSD1306_AVR_FONT5X7_H
    def run_flask():
#define SSD1306_AVR_FONT5X7_H
        app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)


#pragma once
    flask_thread = threading.Thread(target=run_flask, daemon=True)
#include <stdint.h>
    flask_thread.start()


extern const uint8_t font5x7[][5];
    try:
extern const uint8_t font_accent[][5];
        usb_context = usb1.USBContext()
        usb_handle = usb_context.openByVendorIDAndProductID(VENDOR_ID, PRODUCT_ID)


// Optional helpers to know counts (adjust if needed)
        if usb_handle is None:
#define FONT5X7_FIRST 32
            print("ERREUR : périphérique LUFA non trouvé.")
#define FONT5X7_LAST  126
            print("Vérifie les VID/PID et la connexion du lecteur.")
#define FONT5X7_COLS  5
            sys.exit(1)


#endif //SSD1306_AVR_FONT5X7_H
        print(f"Périphérique trouvé (VID:0x{VENDOR_ID:04X}, PID:0x{PRODUCT_ID:04X})")
</syntaxhighlight>Lien Github du librairie : https://github.com/laurrnci22/AccessBadge/tree/main/lufa-LUFA-210130-NSI/I2L/Minimal/libs/libssd1306-atmega328p


'''<br />b) Librairie PN532'''
        try:
            if usb_handle.kernelDriverActive(0):
                usb_handle.detachKernelDriver(0)
        except usb1.USBErrorNotFound:
            pass


Cette nouvelle librairie PN532 permet d’assurer un '''fonctionnement fiable du module NFC''' en utilisant le '''protocole de communication I2C''', et de '''lire correctement les données contenues dans le badge'''.
        usb_handle.claimInterface(0)
        print("Interface 0 revendiquée, lecture/écriture prêtes.\n")


Les principaux objectifs de cette adaptation étaient :
        monitor_usb()


* garantir une '''communication I2C stable''' avec le PN532,
    except usb1.USBErrorAccess:
* assurer une '''lecture fiable des éléments du badge''',
        print("\nPermission refusée. Lance le script avec sudo :")
* simplifier l’intégration avec le microcontrôleur,
        print("   sudo python3 usb_service.py")
* mieux maîtriser les échanges bas niveau avec le module NFC.
    except Exception as e:
<syntaxhighlight lang="c" line="1">
        print(f"\nErreur d'initialisation USB : {e}")
#include <stdbool.h>
        import traceback
#include <stdint.h>
        traceback.print_exc()
#include <stddef.h>


#ifndef PN532_LIB_ATMEGA32U4_PN532_H
</syntaxhighlight>
#define PN532_LIB_ATMEGA32U4_PN532_H




#define MIFARE_UID_MAX_LENGTH              MIFARE_UID_TRIPLE_LENGTH
'''<big>4. Fonctionnement global</big>'''
#define MIFARE_UID_TRIPLE_LENGTH            (10)
#define MIFARE_CMD_AUTH_A                  (0x60)
#define MIFARE_CMD_AUTH_B                  (0x61)
#define MIFARE_CMD_READ                    (0x30)
#define MIFARE_CMD_WRITE                    (0xA0)
#define MIFARE_CMD_TRANSFER                (0xB0)
#define MIFARE_CMD_DECREMENT                (0xC0)
#define MIFARE_CMD_INCREMENT                (0xC1)
#define MIFARE_CMD_STORE                    (0xC2)
#define MIFARE_ULTRALIGHT_CMD_WRITE        (0xA2)
#define MIFARE_UID_SINGLE_LENGTH            (4)
#define MIFARE_UID_DOUBLE_LENGTH            (7)
#define MIFARE_KEY_LENGTH                  (6)
#define MIFARE_BLOCK_LENGTH                (16)


Le programme fonctionne comme un '''automate fini''', où chaque état correspond à une option du menu (écriture, lecture, informations).


#define PN532_COMMAND_READGPIO              (0x0C)
Les boutons Haut/Bas font '''transiter l’état courant''' vers un autre état, et le bouton Select déclenche l’action associée à l’état actif.
#define PN532_COMMAND_WRITEGPIO            (0x0E)
#define PN532_COMMAND_INDATAEXCHANGE        (0x40)
#define PN532_COMMAND_DIAGNOSE              (0x00)
#define PN532_COMMAND_GETFIRMWAREVERSION    (0x02)
#define PN532_COMMAND_GETGENERALSTATUS      (0x04)
#define PN532_COMMAND_READREGISTER          (0x06)
#define PN532_COMMAND_WRITEREGISTER        (0x08)
#define PN532_COMMAND_SETSERIALBAUDRATE    (0x10)
#define PN532_COMMAND_SETPARAMETERS        (0x12)
#define PN532_COMMAND_SAMCONFIGURATION      (0x14)
#define PN532_COMMAND_POWERDOWN            (0x16)
#define PN532_COMMAND_RFCONFIGURATION      (0x32)
#define PN532_COMMAND_RFREGULATIONTEST      (0x58)
#define PN532_COMMAND_INJUMPFORDEP          (0x56)
#define PN532_COMMAND_INJUMPFORPSL          (0x46)
#define PN532_COMMAND_INLISTPASSIVETARGET  (0x4A)
#define PN532_COMMAND_INATR                (0x50)
#define PN532_COMMAND_INPSL                (0x4E)
#define PN532_COMMAND_INCOMMUNICATETHRU    (0x42)
#define PN532_COMMAND_INDESELECT            (0x44)
#define PN532_COMMAND_INRELEASE            (0x52)
#define PN532_COMMAND_INSELECT              (0x54)
#define PN532_COMMAND_INAUTOPOLL            (0x60)
#define PN532_COMMAND_TGINITASTARGET        (0x8C)
#define PN532_COMMAND_TGSETGENERALBYTES    (0x92)
#define PN532_COMMAND_TGGETDATA            (0x86)
#define PN532_COMMAND_TGSETDATA            (0x8E)
#define PN532_COMMAND_TGSETMETADATA        (0x94)
#define PN532_COMMAND_TGGETINITIATORCOMMAND (0x88)
#define PN532_COMMAND_TGRESPONSETOINITIATOR (0x90)
#define PN532_COMMAND_TGGETTARGETSTATUS    (0x8A)
#define PN532_PREAMBLE                      (0x00)
#define PN532_STARTCODE1                    (0x00)
#define PN532_STARTCODE2                    (0xFF)
#define PN532_POSTAMBLE                    (0x00)
#define PN532_HOSTTOPN532                  (0xD4)
#define PN532_PN532TOHOST                  (0xD5)
#define PN532_ERROR_NONE                                                (0x00)


Après chaque action, l’automate revient dans l’état du menu sélectionné et attend la prochaine interaction.
Voici le code correspondant :<syntaxhighlight lang="c">
int main(void) {
    MenuOption current_selection = MENU_WRITE;


#define NTAG2XX_BLOCK_LENGTH                (4)
    init_system();
    menu_display(current_selection);


    while (1) {
        USB_USBTask();


/* Define */
        if (button_up_pressed()) {
#define PN532_MIFARE_ISO14443A              (0x00)
            current_selection = (current_selection == 0) ? MENU_COUNT - 1 : current_selection - 1;
#define PN532_STATUS_ERROR                                              (-1)
            menu_display(current_selection);
#define PN532_STATUS_OK                                                (0)
        }


        if (button_down_pressed()) {
            current_selection = (current_selection == MENU_COUNT - 1) ? 0 : current_selection + 1;
            menu_display(current_selection);
        }


typedef struct _PN532 {
        if (button_select_pressed()) {
    int (*reset)(void);
            switch (current_selection) {
    int (*read_data)(uint8_t* data, uint16_t count);
                case MENU_WRITE: action_write(); break;
    int (*write_data)(uint8_t *data, uint16_t count);
                case MENU_READ:  action_read();  break;
    bool (*wait_ready)(uint32_t timeout);
                case MENU_INFOS: action_infos(); break;
     int (*wakeup)(void);
            }
     void (*log)(const char* log);
            menu_display(current_selection);
} PN532;
        }
     }
     return 0;
}


int PN532_WriteFrame(PN532* pn532, uint8_t* data, uint16_t length);
</syntaxhighlight><big>'''5. Librairies'''</big>
int PN532_ReadFrame(PN532* pn532, uint8_t* buff, uint16_t length);
int PN532_CallFunction(PN532* pn532, uint8_t command, uint8_t* response, uint16_t response_length, uint8_t* params, uint16_t params_length, uint32_t timeout);
int PN532_GetFirmwareVersion(PN532* pn532, uint8_t* version);
int PN532_SamConfiguration(PN532* pn532);
int PN532_ReadPassiveTarget(PN532* pn532, uint8_t* response, uint8_t card_baud, uint32_t timeout);
int PN532_MifareClassicAuthenticateBlock(PN532* pn532, uint8_t* uid, uint8_t uid_length, uint16_t block_number, uint16_t key_number, uint8_t* key);
int PN532_MifareClassicReadBlock(PN532* pn532, uint8_t* response, uint16_t block_number);
int PN532_MifareClassicWriteBlock(PN532* pn532, uint8_t* data, uint16_t block_number);
int PN532_Ntag2xxReadBlock(PN532* pn532, uint8_t* response, uint16_t block_number);
int PN532_Ntag2xxWriteBlock(PN532* pn532, uint8_t* data, uint16_t block_number);
int PN532_ReadGpio(PN532* pn532, uint8_t* pins_state);
bool PN532_ReadGpioP(PN532* pn532, uint8_t pin_number);
bool PN532_ReadGpioI(PN532* pn532, uint8_t pin_number);
int PN532_WriteGpio(PN532* pn532, uint8_t* pins_state);
int PN532_WriteGpioP(PN532* pn532, uint8_t pin_number, bool pin_state);


Voici les principales fonctions utilisées dans les bibliothèques suivantes :


#endif //PN532_LIB_ATMEGA32U4_PN532_H
* '''i2c-lib-atmega32u4'''<syntaxhighlight lang="c">
void i2c_init(void) {
    TWBR = (uint8_t)TWBR_val;
}
 
void i2c_start_write(uint8_t address) {
    TWCR = (1<<TWSTA)|(1<<TWEN)|(1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
    TWDR = (address << 1); // écriture
    TWCR = (1<<TWEN)|(1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
}
void i2c_start_read(uint8_t address) {
    TWCR = (1<<TWSTA)|(1<<TWEN)|(1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
    TWDR = (address << 1) | 1; // bit R/W = 1 pour lecture
    TWCR = (1<<TWEN)|(1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
}
 
void i2c_stop(void) {
    TWCR = (1<<TWSTO)|(1<<TWEN)|(1<<TWINT);
    _delay_us(10);
}
 
void i2c_write(uint8_t data) {
    TWDR = data;
    TWCR = (1<<TWEN)|(1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
}
uint8_t i2c_read(uint8_t ack) {
    if (ack)
        TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA);
    else
        TWCR = (1<<TWEN)|(1<<TWINT);
    while (!(TWCR & (1<<TWINT)));
    return TWDR;
}
</syntaxhighlight>
</syntaxhighlight>
*'''pn532-lib-atmega32u4'''<syntaxhighlight lang="c">
int PN532_I2C_ReadData(uint8_t* data, uint16_t count) {
    i2c_start_read(PN532_I2C_ADDRESS);
    uint8_t status = i2c_read(1); // read status + ACK
    if (status != PN532_I2C_READY) {
        i2c_stop();
        return PN532_STATUS_ERROR;
    }


<big>'''2. Mode connecté USB'''</big>
    for (uint16_t i = 0; i < count; i++) {
        data[i] = i2c_read(i < (count - 1));
    }
    i2c_stop();
    return PN532_STATUS_OK;
}


On propose aussi un mode connecté USB sur PC pour pouvoir gérer les informations présentes sur le badge :
int PN532_I2C_WriteData(uint8_t* data, uint16_t count) {
    i2c_start_write(PN532_I2C_ADDRESS);
    for (uint16_t i = 0; i < count; i++) {
        i2c_write(data[i]);
    }
    i2c_stop();
    return PN532_STATUS_OK;
}


'''a) Communication avec le périphérique USB : libusb'''<syntaxhighlight lang="c" line="1">
bool PN532_I2C_WaitReady(uint32_t timeout_ms) {
#include <stdio.h>
    uint8_t status;
#include <stdlib.h>
    uint32_t waited = 0;
#include <unistd.h>
#include <time.h>
#include <libusb-1.0/libusb.h>


#define VENDOR_ID  0x4242
    while (waited < timeout_ms) {
#define PRODUCT_ID 0x0001
        i2c_start_read(PN532_I2C_ADDRESS);
        status = i2c_read(0);
        i2c_stop();
        if (status == PN532_I2C_READY) {
            return true;
        }
        _delay_ms(5);
        waited += 5;
    }
    return false;
}


#define EP_OUT 0x02  // MYOUT_EPADDR (OUT)
int PN532_I2C_Wakeup(void) {
#define EP_IN  0x81  // MYIN_EPADDR  (IN)
    uint8_t wakeup[] = {0x55, 0x55, 0x00, 0x00, 0x00};
#define EP_SIZE 8     // Taille des endpoints
    i2c_start_write(PN532_I2C_ADDRESS);
#define TIMEOUT 100 // Timeout en ms
    for (uint8_t i = 0; i < sizeof(wakeup); i++) {
#define TIMEOUT 100  // Timeout en ms
        i2c_write(wakeup[i]);
    }
     i2c_stop();
    _delay_ms(100);
    return PN532_STATUS_OK;
}


int main(void)
int PN532_Reset(void) {
{
     _delay_ms(400);
     libusb_context *ctx = NULL;
     return PN532_STATUS_OK;
     libusb_device_handle *dev_handle = NULL;
}
    int r;


     // Initialisation libusb
void PN532_I2C_Init(PN532* pn532) {
     r = libusb_init(&ctx);
    i2c_init();
     if (r < 0) {
 
         fprintf(stderr, "Erreur libusb_init: %d\n", r);
    pn532->reset = PN532_Reset;
        return 1;
    pn532->read_data = PN532_I2C_ReadData;
     pn532->write_data = PN532_I2C_WriteData;
    pn532->wait_ready = PN532_I2C_WaitReady;
    pn532->wakeup = PN532_I2C_Wakeup;
  // pn532->log = PN532_Log;
 
     pn532->reset();
     pn532->wakeup();
}
</syntaxhighlight>
*'''pn532-lib-atmega32u4'''
<syntaxhighlight lang="c">
#define GLYPH_WIDTH 5
#define GLYPH_SPACING 1
#define GLYPH_PX (GLYPH_WIDTH + GLYPH_SPACING)
 
// Draw a 5x7 glyph on the screen
static void draw_glyph5x7(const uint8_t glyph[GLYPH_WIDTH]) {
    for (uint8_t i = 0; i < GLYPH_WIDTH; ++i) {
         ssd1306_send_data_byte(glyph[i]);
     }
     }
    ssd1306_send_data_byte(0x00); // 1px spacing
}


    // Ouvrir le périphérique LUFA
// Decode next glyph from a limited UTF-8 set (ASCII included)
     dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID);
// Returns number of bytes consumed and updates *out
     if (!dev_handle) {
static uint8_t next_glyph(const char *s, const uint8_t **out) {
         fprintf(stderr, "LUFA non trouvé\n");
     unsigned char c = (unsigned char)s[0];
        libusb_exit(ctx);
     if (c == 0x00) {
         return 1;
         *out = NULL;
         return 0;
     }
     }


     // Détacher driver kernel si attaché
     // Handle accented UTF-8 letters
     if (libusb_kernel_driver_active(dev_handle, 0))
     if (c == 0xC3 && s[1]) {
         libusb_detach_kernel_driver(dev_handle, 0);
        unsigned char t = (unsigned char)s[1];
        switch (t) {
            case 0xA9: *out = font_accent[0]; return 2; // é
            case 0x88: *out = font_accent[1]; return 2; // è
            case 0x80: *out = font_accent[2]; return 2; // à
            case 0x94: *out = font_accent[3]; return 2; // ô
            case 0x99: *out = font_accent[4]; return 2; // ù
            case 0xA7: *out = font_accent[5]; return 2; // ç
            default:
                *out = font5x7['?' - FONT5X7_FIRST];
                return 2;
         }
    }
 
    // Treat all other characters (including ASCII)
    if (c < FONT5X7_FIRST || c > FONT5X7_LAST) c = '?';
    *out = font5x7[c - FONT5X7_FIRST];
    return 1;
}


    // Claim de l'interface
// Measure the width in pixels of a string (up to '\n')
     r = libusb_claim_interface(dev_handle, 0);
uint16_t measure_string_px(const char *s) {
     if (r != 0) {
     uint16_t count = 0;
         fprintf(stderr, "Impossible de claim l'interface: %d\n", r);
     while (*s && *s != '\n') {
         libusb_close(dev_handle);
         const uint8_t *glyph = NULL;
         libusb_exit(ctx);
        uint8_t consumed = next_glyph(s, &glyph);
         return 1;
         if (!consumed) break;
         count++;
         s += consumed;
     }
     }
    return count * GLYPH_PX;
}
static void print_string_core(const char *s, uint8_t start_page, uint8_t start_col,
                              uint16_t max_width_px, bool center, uint16_t scroll_px) {
    if (!s || start_page >= SSD1306_PAGES) return;
    uint16_t base_col = start_col < SSD1306_WIDTH ? start_col : 0;
    uint16_t line_right = max_width_px ? base_col + max_width_px : SSD1306_WIDTH;
    if (line_right > SSD1306_WIDTH) line_right = SSD1306_WIDTH;
    uint8_t page = start_page;
    uint16_t col = base_col;
    while (*s && page < SSD1306_PAGES) {
        // Handle new line
        if (*s == '\n') {
            page++;
            if (page >= SSD1306_PAGES) break;
            col = base_col;
            ssd1306_set_cursor(page, (uint8_t)col);
            s++;
            continue;
        }


    printf("=== Monitoring USB ===\n");
        const uint8_t *glyph = NULL;
    printf("Appuyez sur Ctrl+C pour arrêter\n\n");
        uint8_t consumed = next_glyph(s, &glyph);
        if (!consumed) break;


    // Boucle de monitoring continu
        // Scroll adjustment
    while (1) {
        if (scroll_px >= GLYPH_PX) {
         unsigned char in_msg[EP_SIZE] = {0};
            scroll_px -= GLYPH_PX;
        int transferred;
            s += consumed;
        r = libusb_interrupt_transfer(dev_handle, EP_IN, in_msg, EP_SIZE, &transferred, TIMEOUT);
            continue;
         }
 
        // Wrap to next line if glyph exceeds line width
        if ((col + GLYPH_PX) > line_right) {
            page++;
            if (page >= SSD1306_PAGES) break;
            col = base_col;
            ssd1306_set_cursor(page, (uint8_t)col);
        }


         if (r == 0 && transferred > 0) {
        // Center adjustment (ignored if scrolling)
             printf("[%ld] %.*s\n", time(NULL), transferred, in_msg);
         if (center && scroll_px == 0 && col == base_col) {
             fflush(stdout); // Force l'affichage immédiat
             uint16_t remaining = 0;
            const char *line_start = s;
            while (*line_start && *line_start != '\n') {
                const uint8_t *tmp_glyph = NULL;
                uint8_t tmp_consumed = next_glyph(line_start, &tmp_glyph);
                if (!tmp_consumed) break;
                remaining += GLYPH_PX;
                line_start += tmp_consumed;
            }
            uint16_t draw_w = (remaining > (line_right - base_col)) ? (line_right - base_col) : remaining;
            col += ((line_right - base_col) - draw_w) / 2u;
             ssd1306_set_cursor(page, (uint8_t)col);
         }
         }


         usleep(10000); // 10ms entre chaque lecture
         draw_glyph5x7(glyph);
        col += GLYPH_PX;
        s += consumed;
     }
     }
}


    // Libération (jamais atteint sauf si Ctrl+C)
    libusb_release_interface(dev_handle, 0);
    libusb_close(dev_handle);
    libusb_exit(ctx);


     return 0;
 
// Public functions
 
// Print UTF-8 string with optional max width and optional scroll offset
void ssd1306_print_utf8_with_max_width_scroll(const char *s, uint8_t start_page, uint8_t start_col,
                                              uint8_t max_width_px, uint16_t scroll_px) {
     print_string_core(s, start_page, start_col, max_width_px, false, scroll_px);
}
}


</syntaxhighlight>'''a) Code LUFA'''
// Print UTF-8 string across the full width
void ssd1306_print_utf8(const char *s, uint8_t start_page, uint8_t start_col) {
    print_string_core(s, start_page, start_col, 0, false, 0);
}


Voici les codes permettant à notre microcontrôleur de se comporter comme un périphérique USB lorsqu’il est connecté à un ordinateur.
// Print UTF-8 string centered on the page
void ssd1306_print_utf8_center(const char *s, uint8_t page) {
    print_string_core(s, page, 0, 0, true, 0);
}
// Print UTF-8 string with optional max width
void ssd1306_print_utf8_with_max_width(const char *s, uint8_t start_page, uint8_t start_col, uint8_t max_width_px) {
    print_string_core(s, start_page, start_col, max_width_px, false, 0);
}




// Print a single character at a specific page/column
void ssd1306_putc(char c, uint8_t page, uint8_t col) {
    const uint8_t *glyph = NULL;
    char s[2] = {c, 0};
    next_glyph(s, &glyph);
    ssd1306_set_cursor(page, col);
    draw_glyph5x7(glyph);
}


// Scroll a long UTF-8 string horizontally on a single line
// scroll_offset: number of pixels to shift to the left
void ssd1306_print_utf8_scroll(const char *s, uint8_t page, uint8_t start_col,
                              uint8_t max_width_px, uint16_t scroll_offset) {
    if (!s || page >= SSD1306_PAGES) return;


    uint16_t base_col = start_col < SSD1306_WIDTH ? start_col : 0;
    uint16_t line_right = base_col + max_width_px;
    if (line_right > SSD1306_WIDTH) line_right = SSD1306_WIDTH;


    // Clear only this line/page
    ssd1306_set_cursor(page, 0);
    for (uint16_t i = 0; i < SSD1306_WIDTH; i++) {
        ssd1306_send_data_byte(0x00);
    }


<big>'''b) Code minimal/LUFA'''</big>
    uint16_t col = base_col;
    const char *p = s;
    uint16_t skipped_px = 0;


<big>'''4. Librairies'''</big>
    ssd1306_set_cursor(page, (uint8_t)col);


'''a) Écran OLED'''
    while (*p && col < line_right) {
        const uint8_t *glyph = NULL;
        uint8_t consumed = next_glyph(p, &glyph);
        if (!consumed) break;


'''b) Protocole de communication : I2C'''
        // Skip pixels until scroll_offset is reached
        if (skipped_px + GLYPH_PX <= scroll_offset) {
            skipped_px += GLYPH_PX;
            p += consumed;
            continue;
        }


        // Draw visible glyph
        draw_glyph5x7(glyph);
        col += GLYPH_PX;
        p += consumed;
    }
}
</syntaxhighlight>
= Démonstrations =
= Démonstrations =
[[Fichier:Menu.mp4|Affichage du menu déroulant|vignette|centré]]
[[Fichier:Mode autonome.mp4|vignette|Mode autonome sans PC|centré]]




[[Fichier:Infos.mp4|centré|vignette|Affichage des informations sur le badge]]
[[Fichier:Lecture badge.mp4|vignette|Affichage des informations sur le badge et authentification|centré]]




[[Fichier:Deblocage-app.mp4|centré|vignette|Scan badge + déblocage app]]
[[Fichier:Ecriture badge.mp4|vignette|Écriture sur le badge et affichage des infos|centré]]


= Rendus (étudiants) =
= Rendus (étudiants) =
Ligne 402 : Ligne 924 :
* microcontrôleur : [[File:I2L-2025-Programmes-uC-G8.zip]]
* microcontrôleur : [[File:I2L-2025-Programmes-uC-G8.zip]]
* ordinateur [[File:I2L-2025-Programmes-PC-G8.zip]]
* ordinateur [[File:I2L-2025-Programmes-PC-G8.zip]]
* code source [[File:I2L-2025-Code-Source-G8.zip]]

Version actuelle datée du 11 janvier 2026 à 17:53

Proposition de système (étudiants)

Notre projet consiste à mettre en place un système d’authentification pour une application utilisant un badge électronique NFC. L’objectif est de permettre à un utilisateur de se connecter rapidement et de manière sécurisée sans saisir de mot de passe. L’utilisateur présente son badge au lecteur, si l’identification est valide, l’application se débloque et une LED verte ou un message indique le succès, si l’identification est invalide, l’accès est refusé et une LED rouge ou un message indique l’échec. Chaque tentative peut être enregistrée pour un suivi des accès.

Contre-proposition (intervenant)

Proposez un mode autonome et un mode connecté via USB. Le lecteur NFC PN532 semble pouvoir fonctionner en 3.3V (avec la batterie) ou en 5V (connexion USB).

En mode autonome, vous pouvez par exemple afficher des informations sur la carte ou le badge identifié. En mode connecté vous pouvez effectivement débloquer une application. L'application communiquera avec la carte via la classe USB "vendeur" (spécifique). Il suffit d'une interruption IN pour savoir si une carte a été identifiée.

Une bibliothèque C pour gérer un lecteur PN532 semble disponible [1].

Une bibliothèque pour gérer le contrôleur graphique SDD1306 est aussi disponible [2].

Proposition définitive (étudiants)

Mise en place d’un système d’authentification pour une application reposant sur l’utilisation d’un badge électronique NFC.

Après le scan du badge, l’utilisateur accède à un menu permettant :

  • d’authentifier et de déverrouiller l’application,
  • d’écrire ou de mettre à jour les informations stockées sur le badge.
  • de lire les données présentes sur le badge (nom, prénom, mot de passe, etc.),

L’application propose deux modes de fonctionnement :

  • un mode autonome,
  • un mode connecté via USB.

Une interface dédiée est également disponible pour les besoins de test et de validation du système.

Répartition du travail (étudiants)

Concernant la répartition des tâches, l’un a travaillé sur le mode autonome tandis que l’autre s’est concentré sur le mode connecté via USB. Toutefois, nous avons constamment collaboré et nous nous sommes mutuellement aidés tout au long du projet.

Carte

Schéma initial (étudiants)

Schéma de la carte

Carte routée (intervenant)

Vous utiliserez la carte avec l'écran OLED. Vous avez deux exemplaires de la carte suivant que vous souhaitez utiliser une connexion I2C ou série avec le lecteur NFC.

Composants (intervenant)

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)

Carte au 13/10/2025

Voici ce qu'on a pu tester :

- Clignotement des LEDs avec des boutons respectifs

- Mise en place de l'horloge (1000ms)

Carte au 16/10/2025

Voici ce qu'on a pu tester :

- Test de l'écran OLED : problème avec l'écran

- Test du NFC I2C en cours

- Test du NFC SPI en cours

Carte au 13/11/2025

- Écran OLED

On a créé une librairie SSD1306 (inspirée d'une déjà existante) pour faire fonctionner notre écran avec le microcontrôleur.

- Mode autonome

On a réussi à communiquer le microcontrôleur avec le NFC via I2C, pour cela, on a créé une librairie PN532 (inspirée d'une déjà existante).

- Mode connecté USB

On a réussi à transformer le microcontrôleur en périphérique USB en utilisant LUFA. Alors, on a pu échanger des données de notre microcontrôleur vers le PC et a pu débloqué le site facilement.

On a réussi à écrire sur la carte (badge) pour stocker des informations.

- Remarque :

Pour le badge (bleu), on a constaté qu'il faudrait avoir une certaine distance pour que ça fonctionne, et le blanc (petit rectangle) il faut coller directement sur le lecteur NFC.

Rendu Final

Description du Projet

L'objectif de ce projet est de réaliser un système d'authentification physique pour une application Web via la technologie NFC. Le système repose sur une architecture matérielle composée d'un microcontrôleur ATmega32u4 et d'un contrôleur NFC PN532. La partie logicielle comprend une application Web de démonstration et une API Python (faisant office de passerelle) pour assurer la communication série (USB) entre le matériel et l'interface Web.

Le système propose deux modes de fonctionnement :

  1. Mode autonome : Ce mode permet d'utiliser le lecteur NFC de manière indépendante. Grâce à un menu interactif sur l'écran OLED, l'utilisateur peut lire et visualiser les informations des badges (UID, nom, prénom) directement sur le boîtier, sans nécessiter de connexion à un ordinateur.
  2. Mode connecté (USB) : Une fois le module connecté à l'ordinateur, ce mode active la communication avec l'API Python. Il permet trois actions principales :
    • Lecture étendue : Transmission des données du badge scanné vers l'ordinateur.
    • Authentification Web : Déverrouillage de l'application Web par simple scan d'un badge enregistré.
    • Écriture : Enregistrement de nouveaux utilisateurs en écrivant les données (nom, prénom, mot de passe) sur une carte vierge depuis l'interface.

Matériel utilisé :

Carte complete intégrant le lecteur NFC
Badge NFC
Badge NFC 2.png

PS: Pour le badge (bleu), on a constaté qu'il faudrait avoir une certaine distance pour que ça fonctionne, et le blanc (petit rectangle) il faut coller directement sur le lecteur NFC.

La réalisation de ce projet nécessitait l'interaction avec deux périphériques principaux : le lecteur NFC et l'écran OLED SSD1306. La plupart des solutions open-source existantes (comme ssd1306 ou pn532-lib) étant conçues spécifiquement pour l'environnement Arduino, elles n'étaient pas directement compatibles avec notre architecture logicielle sur l'ATmega32u4.

Nous avons donc développé nos propres bibliothèques en nous inspirant de l’existant :

  1. Communication I2C : Nous avons d'abord écrit une bibliothèque bas niveau pour gérer le protocole I2C sur le microcontrôleur. C'est la fondation utilisée par nos autres pilotes.
  2. Gestion de l'écran OLED : En nous basant sur la bibliothèque de lexus2k, nous avons réécrit un pilote optimisé et compatible avec notre couche I2C.
  3. Gestion du lecteur NFC : De même, pour le PN532, nous avons adapté les bibliothèques Arduino existantes pour créer une version autonome utilisant notre pilote I2C.

Extraits significatifs de code (étudiants)

1. Badge NFC

Les données d’un badge NFC sont stockées dans des blocs mémoire bien définis, chacun étant identifié par une adresse numérique.

Le badge est donc découpé en plusieurs blocs, et chaque bloc peut contenir une information spécifique.

Dans notre cas, nous avons choisi d’organiser les données de la manière suivante : le nom est stocké dans le bloc 1, le prénom dans le bloc 2, et le mot de passe dans le bloc 5, comme indiqué ci-dessous.

// Configuration Blocs NFC
#define BLOCK_ADDR_NAME    1
#define BLOCK_ADDR_PRENOM  2
#define BLOCK_ADDR_PASS    5

Cette fonction ci dessous écrit des données dans un bloc spécifique d’un badge NFC après avoir authentifié l’accès. Elle complète le bloc à 16 octets avec des zéros si nécessaire et retourne true si l’écriture réussit, sinon false.

bool helper_write_block(PN532 *pn532, uint8_t block_addr, uint8_t *uid, int32_t uid_len, 
                               uint8_t *data, const char *label) {
    // Préparation du bloc de 16 octets 
    // Remplissage 0 si data plus court
    uint8_t block_data[BLOCK_SIZE];
    memset(block_data, 0, BLOCK_SIZE);
    
    size_t len = strlen((char*)data);
    if (len > BLOCK_SIZE) len = BLOCK_SIZE;
    memcpy(block_data, data, len);

    if (PN532_MifareClassicAuthenticateBlock(pn532, uid, uid_len, block_addr, MIFARE_CMD_AUTH_A, KEY_DEFAULT) != PN532_ERROR_NONE)
        return false;

    return (PN532_MifareClassicWriteBlock(pn532, block_data, block_addr) == PN532_ERROR_NONE);
}

Voici la méthode permettant de lire un bloc et d'afficher son contenu à l'écran :

// Lit un bloc. Si dest_buffer != NULL, copie les données brutes.
bool helper_read_block(PN532 *pn532, uint8_t block_addr, uint8_t *uid, int32_t uid_len, 
                              uint8_t *dest_buffer, const char *label, uint8_t ui_line) {
    uint8_t raw_data[BLOCK_SIZE];
    char display_str[32];
    char safe_content[BLOCK_SIZE + 1];

    // Authentification + Lecture
    if (PN532_MifareClassicAuthenticateBlock(pn532, uid, uid_len, block_addr, MIFARE_CMD_AUTH_A, KEY_DEFAULT) == PN532_ERROR_NONE &&
        PN532_MifareClassicReadBlock(pn532, raw_data, block_addr) == PN532_ERROR_NONE) 
    {
        // Copie vers destination
        if (dest_buffer != NULL)
            memcpy(dest_buffer, raw_data, BLOCK_SIZE);

        // Nettoyage pour affichage
        memcpy(safe_content, raw_data, BLOCK_SIZE);
        safe_content[BLOCK_SIZE] = '\0';
        for (int i = 0; i < BLOCK_SIZE; i++)
            if (!isprint((unsigned char)safe_content[i])) safe_content[i] = ' ';

        snprintf(display_str, sizeof(display_str), "%s: %s", label, safe_content);
        ssd1306_print_utf8_center(display_str, ui_line);
        return true;
    } 
    else {
        snprintf(display_str, sizeof(display_str), "%s: Erreur", label);
        ssd1306_print_utf8_center(display_str, ui_line);
        return false;
    }
}


2. Mode autonome

Pour le mode autonome sans PC, on a créée cette fonction ci-dessous. Elle attend qu’une carte NFC soit présentée, lit ses données principales (nom, prénom), les affiche sur l’écran OLED.

void action_infos(void) {
    uint8_t uid[MIFARE_UID_MAX_LENGTH];
    
    ssd1306_clear();
    ui_wait_card_screen("pour infos...");
    PORTD |= (1 << LED6);

    int32_t uid_len = detect_card(&pn532, uid);
    if (uid_len <= 0) {
        ui_show_msg("Pas de carte", 3);
        _delay_ms(DELAY_MSG_SHORT);
        goto cleanup;
    }

    ssd1306_clear();

    ui_format_and_print_uid(uid, uid_len);

    helper_read_block(&pn532,BLOCK_ADDR_NAME,   uid, uid_len, NULL, "Nom",    3);
    helper_read_block(&pn532,BLOCK_ADDR_PRENOM, uid, uid_len, NULL, "Prenom", 5);

    _delay_ms(SELECTION_DELAY);

cleanup:
    PORTD &= ~(1 << LED6);
    ssd1306_clear();
}


3. Mode connecté USB

On propose aussi un mode connecté USB sur PC pour pouvoir gérer les informations présentes sur le badge.

Cette fonction permet de lire les informations présentes sur le badge NFC et de l'envoyer au PC pour être afficher à l'interface utilisateur :

void action_read(void) {
    uint8_t uid[MIFARE_UID_MAX_LENGTH];
    uint8_t usb_buffer[USB_BUF_SIZE] = {0};
    
    ssd1306_clear();
    ui_wait_card_screen("pour lire...");
    PORTD |= (1 << LED5);

    int32_t uid_len = detect_card(&pn532, uid);
    if (uid_len <= 0) {
        ui_show_msg("Pas de carte", 3);
        _delay_ms(DELAY_MSG_LONG);
        goto cleanup;
    }

    ssd1306_clear();

    usb_buffer[0] = 'R'; 
    int copy_len = (uid_len > 7) ? 7 : uid_len; 
    memcpy(&usb_buffer[OFFSET_UID], uid, copy_len);

    ui_format_and_print_uid(uid, uid_len);
    
    helper_read_block(&pn532, BLOCK_ADDR_NAME,   uid, uid_len, &usb_buffer[OFFSET_NAME],   "Nom",    3);
    helper_read_block(&pn532,BLOCK_ADDR_PRENOM, uid, uid_len, &usb_buffer[OFFSET_PRENOM], "Prenom", 5);
    helper_read_block(&pn532,BLOCK_ADDR_PASS,   uid, uid_len, &usb_buffer[OFFSET_PASS],   "Pass",   7);

    usb_send_response(usb_buffer, USB_BUF_SIZE);
    
    _delay_ms(SELECTION_DELAY * 2);

cleanup:
    PORTD &= ~(1 << LED5);
    ssd1306_clear();
}

Voici la fonction pour envoyer les informations au PC :

void usb_send_response(uint8_t *buffer, uint16_t size) {
    Endpoint_SelectEndpoint(MYIN_EPADDR);
    if (Endpoint_IsINReady()) {
        Endpoint_Write_Stream_LE(buffer, size, NULL);
        Endpoint_ClearIN();
    }
}

Cette méthode ci-dessous permet d'écrire sur le badge NFC via le PC :

void action_write(void) {
    uint8_t uid[MIFARE_UID_MAX_LENGTH];
    uint8_t usb_buffer[USB_BUF_SIZE];
    
    ssd1306_clear();
    ssd1306_print_utf8_center("En attente", 2);
    ssd1306_print_utf8_center("donnees USB...", 4);
    PORTD |= (1 << LED4);

    bool received = false;
    for (int i = 0; i < TIMEOUT_USB_WAIT; i++) {
        USB_USBTask();
        Endpoint_SelectEndpoint(MYOUT_EPADDR);
        if (Endpoint_IsOUTReceived()) {
            if (Endpoint_BytesInEndpoint() >= 52) { // Header(4) + Data(48)
                Endpoint_Read_Stream_LE(usb_buffer, USB_BUF_SIZE, NULL);
                Endpoint_ClearOUT();
                received = true;
                break;
            }
        }
        _delay_ms(50);
    }

    if (!received) {
        ui_show_msg("Timeout USB", 3);
        _delay_ms(DELAY_MSG_SHORT);
        goto cleanup;
    }

    if (usb_buffer[0] != 'W') {
        ui_show_msg("Cmd Invalide", 3);
        _delay_ms(DELAY_MSG_SHORT);
        goto cleanup;
    }

    uint8_t len_n = usb_buffer[1];
    uint8_t len_p = usb_buffer[2];
    uint8_t len_pw = usb_buffer[3];

    // Buffers à écrire max 16 octets + null 
    uint8_t data_name[17] = {0};
    uint8_t data_prenom[17] = {0};
    uint8_t data_pass[17] = {0};

    memcpy(data_name,   &usb_buffer[4],      (len_n > 16) ? 16 : len_n);
    memcpy(data_prenom, &usb_buffer[4 + 16], (len_p > 16) ? 16 : len_p);
    memcpy(data_pass,   &usb_buffer[4 + 32], (len_pw > 16) ? 16 : len_pw);

    ssd1306_clear();
    char tmp_disp[32];
    snprintf(tmp_disp, 32, "Nom: %s", data_name);
    ssd1306_print_utf8_center(tmp_disp, 1);
    snprintf(tmp_disp, 32, "Pre: %s", data_prenom);
    ssd1306_print_utf8_center(tmp_disp, 3);
    snprintf(tmp_disp, 32, "Pas: %s", data_pass);
    ssd1306_print_utf8_center(tmp_disp, 5);
    ssd1306_print_utf8_center("Approchez carte", 7);
    
    _delay_ms(DELAY_MSG_SHORT);

    int32_t uid_len = detect_card(&pn532, uid);
    if (uid_len <= 0) {
        ui_show_msg("Pas de carte", 3);
        _delay_ms(DELAY_MSG_SHORT);
        goto cleanup;
    }

    ui_show_msg("Ecriture...", 3);
    
    bool success = true;
    if (success) success = helper_write_block(&pn532, BLOCK_ADDR_NAME,   uid, uid_len, data_name,   "Nom");
    _delay_ms(20);
    if (success) success = helper_write_block(&pn532,BLOCK_ADDR_PRENOM, uid, uid_len, data_prenom, "Prenom");
    _delay_ms(20);
    if (success) success = helper_write_block(&pn532,BLOCK_ADDR_PASS,   uid, uid_len, data_pass,   "Pass");

    uint8_t response[64] = {0};
    response[0] = 'W';
    response[1] = success ? 0x01 : 0x00;
    usb_send_response(response, 64);

    ui_show_msg(success ? "Ecriture OK!" : "Echec Ecriture", 3);
    _delay_ms(DELAY_MSG_LONG);

cleanup:
    PORTD &= ~(1 << LED4);
    ssd1306_clear();
}

Le code Python ci-dessous permet de communiquer en USB avec le microcontrôleur et sert d’API pour l'application Web

Tout d’abord, on récupère les informations de LUFA :

VENDOR_ID = 0x4242
PRODUCT_ID = 0x0001
EP_IN = 0x81
EP_OUT = 0x02
EP_SIZE = 64
TIMEOUT = 1000

Cette fonction assure la gestion de la communication USB.

def monitor_usb():
    global current_card_info, usb_handle, usb_context

    if usb_context is None or usb_handle is None:
        print("Aucun périphérique USB initialisé, arrêt du monitoring.")
        return

    print("=== Monitoring USB actif ===\n")

    while True:
        try:
            data = usb_handle.interruptRead(EP_IN, EP_SIZE, timeout=TIMEOUT)
            if len(data) > 0:
                print(f"\n[{int(time.time())}] Reçu {len(data)} octets:")
                print("="*60)
                for i in range(0, len(data), 16):
                    hex_part = ' '.join(f'{b:02X}' for b in data[i:i+16])
                    ascii_part = ''.join(chr(b) if 32 <= b < 127 else '.' for b in data[i:i+16])
                    print(f"{i:04d}: {hex_part:<48} | {ascii_part}")
                print("="*60)

                card_info = parse_card_data(data)
                if card_info:
                    current_card_info = card_info
                    print("\n=== DONNÉES PARSÉES ===")
                    for k, v in card_info.items():
                        print(f"{k.capitalize():<10}: {v}")
                    print("="*60 + "\n")

        except usb1.USBErrorTimeout:
            pass
        except usb1.USBErrorNoDevice:
            print("\nPériphérique déconnecté.")
            usb_handle = None
            break
        except Exception as e:
            print(f"\nErreur inattendue: {e}")
            import traceback
            traceback.print_exc()
            break

        time.sleep(0.05)

Cette fonction assure le traitement des données USB entrantes avant de les envoyer vers l'interface Web.

def parse_card_data(data):
    if len(data) < 56:
        print(f"Données trop courtes: {len(data)} octets")
        return None
    if data[0] != ord('R'):
        print(f"Type incorrect: {chr(data[0]) if data[0] < 127 else data[0]}")
        return None

    uid_bytes = [b for b in data[1:8] if b != 0]
    uid = ''.join(f'{b:02X}' for b in uid_bytes)

    nom = bytes(data[8:24]).decode('ascii', errors='ignore').strip('\x00 ')
    prenom = bytes(data[24:40]).decode('ascii', errors='ignore').strip('\x00 ')
    password = bytes(data[40:56]).decode('ascii', errors='ignore').strip('\x00 ')

    return {'uid': uid, 'nom': nom, 'prenom': prenom, 'password': password}

Cette méthode permet d’envoyer les données reçues de l’API vers le périphérique USB

def write_to_badge(nom, prenom, password):
    global usb_handle

    if usb_handle is None:
        return {'success': False, 'error': 'Périphérique USB non connecté'}

    try:
        nom_bytes = nom.encode('ascii')[:16]
        prenom_bytes = prenom.encode('ascii')[:16]
        password_bytes = password.encode('ascii')[:16]

        packet = bytearray(64)
        packet[0] = ord('W')
        packet[1] = len(nom_bytes)
        packet[2] = len(prenom_bytes)
        packet[3] = len(password_bytes)
        packet[4:4+len(nom_bytes)] = nom_bytes
        packet[20:20+len(prenom_bytes)] = prenom_bytes
        packet[36:36+len(password_bytes)] = password_bytes

        print("\n=== ENVOI DONNÉES BADGE ===")
        print(f"Nom: {nom} | Prénom: {prenom} | Pass: {password}")
        usb_handle.interruptWrite(EP_OUT, bytes(packet), timeout=5000)
        print("En attente de la réponse...")

        try:
            response = usb_handle.interruptRead(EP_IN, EP_SIZE, timeout=10000)
            if len(response) > 0 and response[0] == ord('W'):
                success = response[1] == 0x01
                print(f"Réponse reçue: {'OK' if success else 'ERREUR'}")
                return {'success': success, 'message': 'Écriture réussie' if success else 'Erreur lors de l\'écriture'}
            return {'success': False, 'error': 'Réponse invalide'}

        except usb1.USBErrorTimeout:
            return {'success': False, 'error': 'Timeout - Pas de réponse du badge'}

    except Exception as e:
        return {'success': False, 'error': str(e)}

L'API développée en Python

app.route('/api/nfc/status')
def nfc_status():
    return jsonify({
        'usb_connected': usb_handle is not None,
        'card_detected': current_card_info is not None,
        'info': current_card_info
    })

@app.route('/api/nfc/info')
def get_current_info():
    if current_card_info:
        return jsonify(current_card_info)
    return jsonify({'error': 'Aucune carte scannée'}), 404

@app.route('/api/nfc/register', methods=['POST'])
def write_nfc():
    global usb_handle
    if usb_handle is None:
        return jsonify({'error': 'Périphérique USB non connecté', 'success': False}), 503

    data = request.get_json()
    if not data:
        return jsonify({'error': 'Corps JSON manquant', 'success': False}), 400

    nom = data.get('nom', '').strip()
    prenom = data.get('prenom', '').strip()
    password = data.get('password', '').strip()

    if not nom or not prenom or not password:
        return jsonify({'error': 'nom, prenom et password sont requis', 'success': False}), 400

    if len(nom) > 16 or len(prenom) > 16 or len(password) > 16:
        return jsonify({'error': 'Champs trop longs (max 16 caractères)', 'success': False}), 400

    print(f"\n=== REQUÊTE D'ÉCRITURE BADGE ===\nNom: {nom}, Prénom: {prenom}, Password: {password}")
    result = write_to_badge(nom, prenom, password)
    return jsonify(result), (200 if result['success'] else 500)

Ce code sert à démarrer le programme en lançant deux tâches simultanées. D'abord, il active le serveur Web en arrière-plan pour qu'il ne bloque pas le reste du script. Ensuite, il connecte le microcontrôleur en USB (en s'assurant que l'ordinateur lui laisse l'accès exclusif) et lance la boucle de surveillance pour détecter et traiter les badges en temps réel.

if __name__ == '__main__':
    def run_flask():
        app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)

    flask_thread = threading.Thread(target=run_flask, daemon=True)
    flask_thread.start()

    try:
        usb_context = usb1.USBContext()
        usb_handle = usb_context.openByVendorIDAndProductID(VENDOR_ID, PRODUCT_ID)

        if usb_handle is None:
            print("ERREUR : périphérique LUFA non trouvé.")
            print("Vérifie les VID/PID et la connexion du lecteur.")
            sys.exit(1)

        print(f"Périphérique trouvé (VID:0x{VENDOR_ID:04X}, PID:0x{PRODUCT_ID:04X})")

        try:
            if usb_handle.kernelDriverActive(0):
                usb_handle.detachKernelDriver(0)
        except usb1.USBErrorNotFound:
            pass

        usb_handle.claimInterface(0)
        print("Interface 0 revendiquée, lecture/écriture prêtes.\n")

        monitor_usb()

    except usb1.USBErrorAccess:
        print("\nPermission refusée. Lance le script avec sudo :")
        print("    sudo python3 usb_service.py")
    except Exception as e:
        print(f"\nErreur d'initialisation USB : {e}")
        import traceback
        traceback.print_exc()


4. Fonctionnement global

Le programme fonctionne comme un automate fini, où chaque état correspond à une option du menu (écriture, lecture, informations).

Les boutons Haut/Bas font transiter l’état courant vers un autre état, et le bouton Select déclenche l’action associée à l’état actif.

Après chaque action, l’automate revient dans l’état du menu sélectionné et attend la prochaine interaction.

Voici le code correspondant :

int main(void) {
    MenuOption current_selection = MENU_WRITE;

    init_system();
    menu_display(current_selection);

    while (1) {
        USB_USBTask();

        if (button_up_pressed()) {
            current_selection = (current_selection == 0) ? MENU_COUNT - 1 : current_selection - 1;
            menu_display(current_selection);
        }

        if (button_down_pressed()) {
            current_selection = (current_selection == MENU_COUNT - 1) ? 0 : current_selection + 1;
            menu_display(current_selection);
        }

        if (button_select_pressed()) {
            switch (current_selection) {
                case MENU_WRITE: action_write(); break;
                case MENU_READ:  action_read();  break;
                case MENU_INFOS: action_infos(); break;
            }
            menu_display(current_selection);
        }
    }
    return 0;
}

5. Librairies

Voici les principales fonctions utilisées dans les bibliothèques suivantes :

  • i2c-lib-atmega32u4
    void i2c_init(void) {
        TWBR = (uint8_t)TWBR_val;
    }
    
    void i2c_start_write(uint8_t address) {
        TWCR = (1<<TWSTA)|(1<<TWEN)|(1<<TWINT);
        while (!(TWCR & (1<<TWINT)));
        TWDR = (address << 1); // écriture
        TWCR = (1<<TWEN)|(1<<TWINT);
        while (!(TWCR & (1<<TWINT)));
    }
    void i2c_start_read(uint8_t address) {
        TWCR = (1<<TWSTA)|(1<<TWEN)|(1<<TWINT);
        while (!(TWCR & (1<<TWINT)));
        TWDR = (address << 1) | 1; // bit R/W = 1 pour lecture
        TWCR = (1<<TWEN)|(1<<TWINT);
        while (!(TWCR & (1<<TWINT)));
    }
    
    void i2c_stop(void) {
        TWCR = (1<<TWSTO)|(1<<TWEN)|(1<<TWINT);
        _delay_us(10);
    }
    
    void i2c_write(uint8_t data) {
        TWDR = data;
        TWCR = (1<<TWEN)|(1<<TWINT);
        while (!(TWCR & (1<<TWINT)));
    }
    uint8_t i2c_read(uint8_t ack) {
        if (ack)
            TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA);
        else
            TWCR = (1<<TWEN)|(1<<TWINT);
        while (!(TWCR & (1<<TWINT)));
        return TWDR;
    }
    
  • pn532-lib-atmega32u4
    int PN532_I2C_ReadData(uint8_t* data, uint16_t count) {
        i2c_start_read(PN532_I2C_ADDRESS);
        uint8_t status = i2c_read(1); // read status + ACK
        if (status != PN532_I2C_READY) {
            i2c_stop();
            return PN532_STATUS_ERROR;
        }
    
        for (uint16_t i = 0; i < count; i++) {
            data[i] = i2c_read(i < (count - 1));
        }
        i2c_stop();
        return PN532_STATUS_OK;
    }
    
    int PN532_I2C_WriteData(uint8_t* data, uint16_t count) {
        i2c_start_write(PN532_I2C_ADDRESS);
        for (uint16_t i = 0; i < count; i++) {
            i2c_write(data[i]);
        }
        i2c_stop();
        return PN532_STATUS_OK;
    }
    
    bool PN532_I2C_WaitReady(uint32_t timeout_ms) {
        uint8_t status;
        uint32_t waited = 0;
    
        while (waited < timeout_ms) {
            i2c_start_read(PN532_I2C_ADDRESS);
            status = i2c_read(0);
            i2c_stop();
            if (status == PN532_I2C_READY) {
                return true;
            }
            _delay_ms(5);
            waited += 5;
        }
        return false;
    }
    
    int PN532_I2C_Wakeup(void) {
        uint8_t wakeup[] = {0x55, 0x55, 0x00, 0x00, 0x00};
        i2c_start_write(PN532_I2C_ADDRESS);
        for (uint8_t i = 0; i < sizeof(wakeup); i++) {
            i2c_write(wakeup[i]);
        }
        i2c_stop();
        _delay_ms(100);
        return PN532_STATUS_OK;
    }
    
    int PN532_Reset(void) {
        _delay_ms(400);
        return PN532_STATUS_OK;
    }
    
    void PN532_I2C_Init(PN532* pn532) {
        i2c_init();
    
        pn532->reset = PN532_Reset;
        pn532->read_data = PN532_I2C_ReadData;
        pn532->write_data = PN532_I2C_WriteData;
        pn532->wait_ready = PN532_I2C_WaitReady;
        pn532->wakeup = PN532_I2C_Wakeup;
       // pn532->log = PN532_Log;
    
        pn532->reset();
        pn532->wakeup();
    }
    
  • pn532-lib-atmega32u4
#define GLYPH_WIDTH 5
#define GLYPH_SPACING 1
#define GLYPH_PX (GLYPH_WIDTH + GLYPH_SPACING)

// Draw a 5x7 glyph on the screen
static void draw_glyph5x7(const uint8_t glyph[GLYPH_WIDTH]) {
    for (uint8_t i = 0; i < GLYPH_WIDTH; ++i) {
        ssd1306_send_data_byte(glyph[i]);
    }
    ssd1306_send_data_byte(0x00); // 1px spacing
}

// Decode next glyph from a limited UTF-8 set (ASCII included)
// Returns number of bytes consumed and updates *out
static uint8_t next_glyph(const char *s, const uint8_t **out) {
    unsigned char c = (unsigned char)s[0];
    if (c == 0x00) {
        *out = NULL;
        return 0;
    }

    // Handle accented UTF-8 letters
    if (c == 0xC3 && s[1]) {
        unsigned char t = (unsigned char)s[1];
        switch (t) {
            case 0xA9: *out = font_accent[0]; return 2; // é
            case 0x88: *out = font_accent[1]; return 2; // è
            case 0x80: *out = font_accent[2]; return 2; // à
            case 0x94: *out = font_accent[3]; return 2; // ô
            case 0x99: *out = font_accent[4]; return 2; // ù
            case 0xA7: *out = font_accent[5]; return 2; // ç
            default:
                *out = font5x7['?' - FONT5X7_FIRST];
                return 2;
        }
    }

    // Treat all other characters (including ASCII)
    if (c < FONT5X7_FIRST || c > FONT5X7_LAST) c = '?';
    *out = font5x7[c - FONT5X7_FIRST];
    return 1;
}

// Measure the width in pixels of a string (up to '\n')
uint16_t measure_string_px(const char *s) {
    uint16_t count = 0;
    while (*s && *s != '\n') {
        const uint8_t *glyph = NULL;
        uint8_t consumed = next_glyph(s, &glyph);
        if (!consumed) break;
        count++;
        s += consumed;
    }
    return count * GLYPH_PX;
}

static void print_string_core(const char *s, uint8_t start_page, uint8_t start_col,
                              uint16_t max_width_px, bool center, uint16_t scroll_px) {
    if (!s || start_page >= SSD1306_PAGES) return;

    uint16_t base_col = start_col < SSD1306_WIDTH ? start_col : 0;
    uint16_t line_right = max_width_px ? base_col + max_width_px : SSD1306_WIDTH;
    if (line_right > SSD1306_WIDTH) line_right = SSD1306_WIDTH;

    uint8_t page = start_page;
    uint16_t col = base_col;

    while (*s && page < SSD1306_PAGES) {
        // Handle new line
        if (*s == '\n') {
            page++;
            if (page >= SSD1306_PAGES) break;
            col = base_col;
            ssd1306_set_cursor(page, (uint8_t)col);
            s++;
            continue;
        }

        const uint8_t *glyph = NULL;
        uint8_t consumed = next_glyph(s, &glyph);
        if (!consumed) break;

        // Scroll adjustment
        if (scroll_px >= GLYPH_PX) {
            scroll_px -= GLYPH_PX;
            s += consumed;
            continue;
        }

        // Wrap to next line if glyph exceeds line width
        if ((col + GLYPH_PX) > line_right) {
            page++;
            if (page >= SSD1306_PAGES) break;
            col = base_col;
            ssd1306_set_cursor(page, (uint8_t)col);
        }

        // Center adjustment (ignored if scrolling)
        if (center && scroll_px == 0 && col == base_col) {
            uint16_t remaining = 0;
            const char *line_start = s;
            while (*line_start && *line_start != '\n') {
                const uint8_t *tmp_glyph = NULL;
                uint8_t tmp_consumed = next_glyph(line_start, &tmp_glyph);
                if (!tmp_consumed) break;
                remaining += GLYPH_PX;
                line_start += tmp_consumed;
            }
            uint16_t draw_w = (remaining > (line_right - base_col)) ? (line_right - base_col) : remaining;
            col += ((line_right - base_col) - draw_w) / 2u;
            ssd1306_set_cursor(page, (uint8_t)col);
        }

        draw_glyph5x7(glyph);
        col += GLYPH_PX;
        s += consumed;
    }
}



// Public functions

// Print UTF-8 string with optional max width and optional scroll offset
void ssd1306_print_utf8_with_max_width_scroll(const char *s, uint8_t start_page, uint8_t start_col,
                                              uint8_t max_width_px, uint16_t scroll_px) {
    print_string_core(s, start_page, start_col, max_width_px, false, scroll_px);
}

// Print UTF-8 string across the full width
void ssd1306_print_utf8(const char *s, uint8_t start_page, uint8_t start_col) {
    print_string_core(s, start_page, start_col, 0, false, 0);
}

// Print UTF-8 string centered on the page
void ssd1306_print_utf8_center(const char *s, uint8_t page) {
    print_string_core(s, page, 0, 0, true, 0);
}
// Print UTF-8 string with optional max width
void ssd1306_print_utf8_with_max_width(const char *s, uint8_t start_page, uint8_t start_col, uint8_t max_width_px) {
    print_string_core(s, start_page, start_col, max_width_px, false, 0);
}


// Print a single character at a specific page/column
void ssd1306_putc(char c, uint8_t page, uint8_t col) {
    const uint8_t *glyph = NULL;
    char s[2] = {c, 0};
    next_glyph(s, &glyph);
    ssd1306_set_cursor(page, col);
    draw_glyph5x7(glyph);
}

// Scroll a long UTF-8 string horizontally on a single line
// scroll_offset: number of pixels to shift to the left
void ssd1306_print_utf8_scroll(const char *s, uint8_t page, uint8_t start_col,
                               uint8_t max_width_px, uint16_t scroll_offset) {
    if (!s || page >= SSD1306_PAGES) return;

    uint16_t base_col = start_col < SSD1306_WIDTH ? start_col : 0;
    uint16_t line_right = base_col + max_width_px;
    if (line_right > SSD1306_WIDTH) line_right = SSD1306_WIDTH;

    // Clear only this line/page
    ssd1306_set_cursor(page, 0);
    for (uint16_t i = 0; i < SSD1306_WIDTH; i++) {
        ssd1306_send_data_byte(0x00);
    }

    uint16_t col = base_col;
    const char *p = s;
    uint16_t skipped_px = 0;

    ssd1306_set_cursor(page, (uint8_t)col);

    while (*p && col < line_right) {
        const uint8_t *glyph = NULL;
        uint8_t consumed = next_glyph(p, &glyph);
        if (!consumed) break;

        // Skip pixels until scroll_offset is reached
        if (skipped_px + GLYPH_PX <= scroll_offset) {
            skipped_px += GLYPH_PX;
            p += consumed;
            continue;
        }

        // Draw visible glyph
        draw_glyph5x7(glyph);
        col += GLYPH_PX;
        p += consumed;
    }
}

Démonstrations


Affichage des informations sur le badge et authentification


Écriture sur le badge et affichage des infos

Rendus (étudiants)

Projet KiCAD : Fichier:I2L-2025-Carte-G8-final.zip

Programmes :