Contrôle dynamique des LEDs sur le port P1 via des pointeurs de fonction

Introduction aux pointeurs de fonction en C

En langage C, un pointeur de fonction est une variable qui stocke l'adresse mémoire d'une fonction. Cette caractéristique permet une grande flexibilité dans la conception de programmes, notamment pour implémenter des fonctions de rappel (callbacks) ou des tables de saut (jump tables). La déclaration d'un tel pointeur doit spécifier le type de retour et la liste des paramètres de la fonction cible.


// Déclaration d'un pointeur de fonction
float (*calcul_ptr)(int, float);

Dans cet exemple, calcul_ptr peut pointer vers n'importe quele fonction acceptant un int et un float comme arguments et retournant un float.

Configuration du port I/O pour les LEDs

Sur un microcontrôleur comme le 8051, le port P1 est un groupe de 8 broches d'E/S. Pour contrôler des LEDs connectées à ce port, il faut d'abord le configurer en mode sortie. Cela s'effectue généralement par écriture dans le registre de direction corespondant.


#include <REGX51.H>

void initialiser_port_leds() {
    // Configure toutes les broches de P1 en sortie
    port_leds = 0x00;
}

Une fois en sortie, l'état de chaque LED est contrôlé par manipulation bit à bit du registre du port. Allumer une LED suppose souvent de mettre sa broche à l'état bas, en supposant un câblage en anode commune.


// Allume la LED sur la broche P1.2
port_leds &= ~(1 << 2);

// Éteint la LED sur la broche P1.2
port_leds |= (1 << 2);

Conception de fonctions d'effets lumineux

Chaque motif lumineux peut être encapsulé dans une fonction autonome. Par exemple, on peut définir des séquences pour un effet de poursuite, de clignotement ou de respiration. Ces fonctions auront généralement la même signature pour être interchangeables via des pointeurs.


void motif_poursuite() {
    static uint8_t position = 0;
    port_leds = ~(1 << position);
    position = (position + 1) % 8;
}

void motif_clignotement() {
    static uint8_t etat = 0;
    port_leds = etat ? 0xFF : 0x00;
    etat = !etat;
}

Tableau de pointeurs de fonction pour la sélection de motifs

Pour gérer plusieurs effets, on peut créer un tableau dont chaque élément est un pointeur vers une fonction d'effet. La sélection du motif à exécuter se fait alors par indexation dans ce tableau.


// Définition du type pour les fonctions d'effet
typedef void (*FonctionMotif)(void);

// Tableau contenant les pointeurs vers les différents motifs
FonctionMotif liste_motifs[] = {
    motif_poursuite,
    motif_clignotement
};

// Nombre de motifs disponibles
#define NOMBRE_MOTIFS (sizeof(liste_motifs) / sizeof(FonctionMotif))

On peut alors appeler le motif désiré de manière dynamique :


uint8_t motif_courant = 0;
liste_motifs[motif_courant](); // Exécute le motif sélectionné

Intégration avec une interruption timer

Pour que les motifs changent automatiquement à intervalles réguliers, on utilise une interruption déclenchée par un timer. La routine de service d'interruption (ISR) peut alors appeler la fonction de motif actif via son pointeur.


volatile FonctionMotif motif_actif = motif_poursuite;

// Routine d'interruption du Timer 0
void isr_timer0(void) interrupt 1 {
    static uint16_t compteur = 0;
    // Recharger le timer pour la prochaine interruption
    TH0 = 0xFC;
    TL0 = 0x18;
    
    // Changer de motif toutes les N interruptions
    if (++compteur >= 50) {
        compteur = 0;
        // Basculer vers le motif suivant dans la liste
        static uint8_t index_motif = 0;
        index_motif = (index_motif + 1) % NOMBRE_MOTIFS;
        motif_actif = liste_motifs[index_motif];
    }
    
    // Appeler le motif actif
    if (motif_actif != NULL) {
        motif_actif();
    }
}

L'initialisation du timer pour générer des interruptions périodiques est une étape cruciale. On configure son mode, sa valeur de précharge et on active l'interruption correspondante.


void configurer_timer() {
    TMOD = 0x01; // Timer 0 en mode 1 (16 bits)
    TH0 = 0xFC;  // Valeur haute pour ~10ms @ 11.0592MHz
    TL0 = 0x18;
    ET0 = 1;     // Active l'interruption du Timer 0
    TR0 = 1;     // Démarre le Timer 0
    EA = 1;      // Active les interruptions globales
}

Considérations pratiques et robustesse

Dans une application réelle, il faut veiller à plusieurs aspects :

  • Initialisation des pointeurs : S'assurer que tous les pointeurs de fonction sont correctement initialisés avant utilisation pour éviter un comportement indéfini.
  • Temps d'exécution des ISR : Les routines d'interruption doivent être les plus courtes possible pour ne pas perturber le système.
  • Protection de l'accès aux variables partagées : Si des variables sont partagées entre l'ISR et le programme principal (comme motif_actif), il peut être nécessaire de désactiver temporairement les interruptions lors de leur modification.

Le modèle présenté offre une arcihtecture modulaire : ajouter un nouveau motif se limite à écrire une nouvelle fonction et à l'ajouter au tableau liste_motifs, sans modifier la logique de gestion de l'interruption.

Étiquettes: C Microcontrôleur 8051 Pointeur de fonction interruption

Publié le 16 juillet à 02h08