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

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


= Extraits significatifs de code (étudiants) =
= Extraits significatifs de code (étudiants) =
'''1. Badge NFC'''
'''<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'''.
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'''.
Ligne 183 : Ligne 183 :
</syntaxhighlight>
</syntaxhighlight>


 
<big><br />'''2. Mode autonome'''</big>
<big>'''1. 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">
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, les logge sur USB, puis remet le système dans son état initial.<syntaxhighlight lang="c">


void action_infos(void) {
void action_infos(void) {
Ligne 208 : Ligne 207 :
     helper_read_block(&pn532,BLOCK_ADDR_PRENOM, uid, uid_len, NULL, "Prenom", 5);
     helper_read_block(&pn532,BLOCK_ADDR_PRENOM, uid, uid_len, NULL, "Prenom", 5);


     // 2. Log UID sur USB (fonctionnalité d'origine)
     _delay_ms(SELECTION_DELAY);
     char uid_str[32] = "UID:";
 
     for (int i=0; i<uid_len; i++) {
cleanup:
        char t[4]; snprintf(t,4,"%02X", uid[i]); strcat(uid_str, t);
    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();
     }
     }
    usb_send_log(uid_str);
}


     _delay_ms(SELECTION_DELAY);
</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:
cleanup:
     PORTD &= ~(1 << LED6);
     PORTD &= ~(1 << LED4);
     ssd1306_clear();
     ssd1306_clear();
}
}
</syntaxhighlight><big>'''2. Mode connecté USB'''</big>
</syntaxhighlight>


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


Voici les codes permettant à notre microcontrôleur de se comporter comme un périphérique USB lorsqu’il est connecté à un ordinateur.
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


On propose aussi un mode connecté USB sur PC pour pouvoir gérer les informations présentes sur le badge :
    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)


<big>'''4. Librairies'''</big>
                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")


'''a) Librairie SSD1306'''
        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


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.
        time.sleep(0.05)


Cette nouvelle version de la librairie SSD1306 permet :
</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


* un meilleur fonctionnement de l’écran OLED,
    uid_bytes = [b for b in data[1:8] if b != 0]
* une affichage plus lisible et plus fluide,
    uid = ''.join(f'{b:02X}' for b in uid_bytes)
* 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">
    nom = bytes(data[8:24]).decode('ascii', errors='ignore').strip('\x00 ')
#ifndef SSD1306_AVR_SSD1306_H
    prenom = bytes(data[24:40]).decode('ascii', errors='ignore').strip('\x00 ')
#define SSD1306_AVR_SSD1306_H
    password = bytes(data[40:56]).decode('ascii', errors='ignore').strip('\x00 ')


#pragma once
    return {'uid': uid, 'nom': nom, 'prenom': prenom, 'password': password}
#include <stdint.h>
#include <stddef.h>


#define SSD1306_I2C_ADDR 0x3C
</syntaxhighlight>
#define SSD1306_CTRL_CMD 0x00
Cette méthode permet d’envoyer les données reçues de l’API vers le périphérique USB<syntaxhighlight lang="python">
#define SSD1306_CTRL_DATA 0x40


#define SSD1306_WIDTH 128
def write_to_badge(nom, prenom, password):
#define SSD1306_HEIGHT 64
    global usb_handle
#define SSD1306_PAGES (SSD1306_HEIGHT/8)


void ssd1306_init(void);
    if usb_handle is None:
void ssd1306_send_command(uint8_t cmd);
        return {'success': False, 'error': 'Périphérique USB non connecté'}
void ssd1306_send_data_byte(uint8_t data);
void ssd1306_send_data(const uint8_t *buf, size_t len);
void ssd1306_set_cursor(uint8_t page, uint8_t col); // page: 0..7, col: 0..127
void ssd1306_clear(void);
void ssd1306_clear_page(uint8_t page);


#endif //SSD1306_AVR_SSD1306_H
    try:
</syntaxhighlight>Voici le code permettant de bien positionner le bloc de texte : <syntaxhighlight lang="c" line="1">
        nom_bytes = nom.encode('ascii')[:16]
#ifndef SSD1306_AVR_SSD1306_TEXT_H
        prenom_bytes = prenom.encode('ascii')[:16]
#define SSD1306_AVR_SSD1306_TEXT_H
        password_bytes = password.encode('ascii')[:16]
#pragma once
#include <stdint.h>


// Print UTF-8 string with optional max width and optional scroll offset
        packet = bytearray(64)
void ssd1306_print_utf8_with_max_width_scroll(const char *s, uint8_t start_page, uint8_t start_col,
        packet[0] = ord('W')
                                              uint8_t max_width_px, uint16_t scroll_px);
        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)}
 
 
</syntaxhighlight>
L'API développée en Python<syntaxhighlight lang="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)
 
</syntaxhighlight>
 
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">
if __name__ == '__main__':
    def run_flask():
        app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)


// Print UTF-8 string across the full width
    flask_thread = threading.Thread(target=run_flask, daemon=True)
void ssd1306_print_utf8(const char *s, uint8_t start_page, uint8_t start_col);
    flask_thread.start()


// Print UTF-8 string with optional max width
    try:
void ssd1306_print_utf8_with_max_width(const char *s, uint8_t start_page, uint8_t start_col, uint8_t max_width_px);
        usb_context = usb1.USBContext()
        usb_handle = usb_context.openByVendorIDAndProductID(VENDOR_ID, PRODUCT_ID)


// Print UTF-8 string centered on the page
        if usb_handle is None:
void ssd1306_print_utf8_center(const char *s, uint8_t page) ;
            print("ERREUR : périphérique LUFA non trouvé.")
// Print a single character at a specific page/column
            print("Vérifie les VID/PID et la connexion du lecteur.")
void ssd1306_putc(char c, uint8_t page, uint8_t col);
            sys.exit(1)


void ssd1306_print_utf8_scroll(const char *s, uint8_t page, uint8_t start_col,
        print(f"Périphérique trouvé (VID:0x{VENDOR_ID:04X}, PID:0x{PRODUCT_ID:04X})")
                              uint8_t max_width_px, uint16_t scroll_offset);
uint16_t measure_string_px(const char *s);


#endif //SSD1306_AVR_SSD1306_TEXT_H
        try:
</syntaxhighlight>Voici le code permettant de formater les caractères affichés à l'écran : <syntaxhighlight lang="c" line="1">
            if usb_handle.kernelDriverActive(0):
#ifndef SSD1306_AVR_FONT5X7_H
                usb_handle.detachKernelDriver(0)
#define SSD1306_AVR_FONT5X7_H
        except usb1.USBErrorNotFound:
            pass


#pragma once
        usb_handle.claimInterface(0)
#include <stdint.h>
        print("Interface 0 revendiquée, lecture/écriture prêtes.\n")


extern const uint8_t font5x7[][5];
        monitor_usb()
extern const uint8_t font_accent[][5];


// Optional helpers to know counts (adjust if needed)
    except usb1.USBErrorAccess:
#define FONT5X7_FIRST 32
        print("\nPermission refusée. Lance le script avec sudo :")
#define FONT5X7_LAST  126
        print("    sudo python3 usb_service.py")
#define FONT5X7_COLS  5
    except Exception as e:
        print(f"\nErreur d'initialisation USB : {e}")
        import traceback
        traceback.print_exc()


#endif //SSD1306_AVR_FONT5X7_H
</syntaxhighlight>
</syntaxhighlight>




Lien Github du librairie : https://github.com/laurrnci22/AccessBadge/tree/main/lufa-LUFA-210130-NSI/I2L/Minimal/libs/libssd1306-atmega328p
'''<big>4. Fonctionnement global</big>'''


'''<br />b) Librairie PN532'''
Le programme fonctionne comme un '''automate fini''', où chaque état correspond à une option du menu (écriture, lecture, informations).


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'''.
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.


Les principaux objectifs de cette adaptation étaient :
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;


* garantir une '''communication I2C stable''' avec le PN532,
    init_system();
* assurer une '''lecture fiable des éléments du badge''',
    menu_display(current_selection);
* simplifier l’intégration avec le microcontrôleur,
* mieux maîtriser les échanges bas niveau avec le module NFC.
<syntaxhighlight lang="c" line="1">
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>


#ifndef PN532_LIB_ATMEGA32U4_PN532_H
    while (1) {
#define PN532_LIB_ATMEGA32U4_PN532_H
        USB_USBTask();


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


#define MIFARE_UID_MAX_LENGTH              MIFARE_UID_TRIPLE_LENGTH
        if (button_down_pressed()) {
#define MIFARE_UID_TRIPLE_LENGTH            (10)
            current_selection = (current_selection == MENU_COUNT - 1) ? 0 : current_selection + 1;
#define MIFARE_CMD_AUTH_A                  (0x60)
            menu_display(current_selection);
#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)


        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;
}


#define PN532_COMMAND_READGPIO              (0x0C)
</syntaxhighlight><big>'''5. Librairies'''</big>
#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)


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


#define NTAG2XX_BLOCK_LENGTH                (4)
* '''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)));
}


/* Define */
void i2c_stop(void) {
#define PN532_MIFARE_ISO14443A              (0x00)
    TWCR = (1<<TWSTO)|(1<<TWEN)|(1<<TWINT);
#define PN532_STATUS_ERROR                                              (-1)
    _delay_us(10);
#define PN532_STATUS_OK                                                (0)
}


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>
*'''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;
    }


typedef struct _PN532 {
     for (uint16_t i = 0; i < count; i++) {
     int (*reset)(void);
        data[i] = i2c_read(i < (count - 1));
    int (*read_data)(uint8_t* data, uint16_t count);
     }
    int (*write_data)(uint8_t *data, uint16_t count);
     i2c_stop();
     bool (*wait_ready)(uint32_t timeout);
     return PN532_STATUS_OK;
     int (*wakeup)(void);
}
     void (*log)(const char* log);
} PN532;


int PN532_WriteFrame(PN532* pn532, uint8_t* data, uint16_t length);
int PN532_I2C_WriteData(uint8_t* data, uint16_t count) {
int PN532_ReadFrame(PN532* pn532, uint8_t* buff, uint16_t length);
    i2c_start_write(PN532_I2C_ADDRESS);
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);
    for (uint16_t i = 0; i < count; i++) {
int PN532_GetFirmwareVersion(PN532* pn532, uint8_t* version);
        i2c_write(data[i]);
int PN532_SamConfiguration(PN532* pn532);
    }
int PN532_ReadPassiveTarget(PN532* pn532, uint8_t* response, uint8_t card_baud, uint32_t timeout);
    i2c_stop();
int PN532_MifareClassicAuthenticateBlock(PN532* pn532, uint8_t* uid, uint8_t uid_length, uint16_t block_number, uint16_t key_number, uint8_t* key);
    return PN532_STATUS_OK;
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);


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;
}


#endif //PN532_LIB_ATMEGA32U4_PN532_H
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();
}
</syntaxhighlight>
</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
}
// 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;
    }
}




Lien Github du librairie : https://github.com/laurrnci22/AccessBadge/tree/main/lufa-LUFA-210130-NSI/I2L/Minimal/libs/libpn532-atmega328p
// 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;
    }
}
</syntaxhighlight>
= Démonstrations =
= Démonstrations =
[[Fichier:Mode autonome.mp4|vignette|Mode autonome sans PC|centré]]
[[Fichier:Mode autonome.mp4|vignette|Mode autonome sans PC|centré]]
Ligne 450 : 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 :