Contrôle du capteur de luminosité BH1750 via I2C logiciel sur STM32Cube

Ce guide présente une implémentation logicielle pour piloter le capteur de luminosité BH1750 (souvent désigné par GY-30) en utilisant une interface I2C logicielle sur des microcontrôleurs STM32, spécifiquement avec l'environnement STM32Cube et la bibliothèque HAL.

Prérequis

Ce pilote est conçu pour fonctionner avec la famille de microcontrôleurs STM32 et exploite la méthode de développeemnt STM32Cube avec la bibliothèque HAL.

Connexion du Capteur

  • Connectez la broche VCC du BH1750 à une source d'alimentation de 3 à 5 volts.
  • Connnectez les deux lignes de signal (SDA et SCL) aux broches GPIO de votre choix sur le microcontrôleur STM32.

Configuration STM32CubeMX

  1. I2C Logiciel :

    • Sélectionnez deux broches GPIO pour les fonctions SCL et SDA de l'I2C lgoiciel. Dans cet exemple, PB8 et PB9 sont utilisés.
    • Il est recommandé de nommer ces broches avec des étiquettes utilisateur telles que softI2C1_SCL et softI2C1_SDA pour une meilleure lisibilité.
    • Configurez ces broches en tant que sorties Open-Drain avec activation de la résistance de pull-up et une vitesse de sortie High .
  2. Temporisation pour Délais Milliseconde :

    • Choisissez un périphérique Timer (par exemple, TIM3).
    • Configurez le Timer pour qu'il compte chaque milliseconde. Aucune interruption n'est nécessaire pour cette fonction.

    Dans la configuration du Timer, si la fréquence du cœur est de 72 MHz, pour obtenir un délai de 1 microseconde (1 µs), le Prescaler doit être réglé sur 71 ((72 * 10^6 / 1 * 10^6) - 1 = 71). Un Prescaler de 0 correspond à une division par 1, d'où la soustraction de 1.

Une fois la configuration effectuée dans STM32CubeMX, intégrez le code du pilote ci-dessous dans votre projet. Vous pourrez ensuite appeler les fonctions correspondantes pour interagir avec le capteur.

Fichier d'En-tête (bh1750.h)


#ifndef __BH1750_H__
#define __BH1750_H__

#include "main.h"

// Interface pour le programme principal :
// Retourne l'adresse d'une variable pour stocker la luminosité lue.
// Retourne l'état de la lecture (succès/échec).
uint8_t getBH1750_Lux(uint16_t *currentLux);

// Fonctions de base pour l'I2C logiciel
void SW_I2C_StartCondition(void);
void SW_I2C_StopCondition(void);
void SW_I2C_SendByte(uint8_t dataByte);
uint8_t SW_I2C_ReceiveByte(void);
void SW_I2C_SendAck(uint8_t ackState);
uint8_t SW_I2C_ReceiveAck(void);

#endif

Fichier d'Implémentation (bh1750.c)


/*
 * Description : Pilote pour le capteur de luminosité BH1750 (GY-30)
 *               via une interface I2C logicielle sur STM32.
 *
 * Configuration STM32CubeMX requise :
 * 1. I2C Logiciel :
 *    - Sélectionner deux broches GPIO pour SCL et SDA.
 *    - Nommage suggéré : softI2C1_SCL, softI2C1_SDA.
 *    - Configuration : Sortie Open-Drain, Pull-up activé, Vitesse High.
 * 2. Temporisation pour délai en microsecondes :
 *    - Choisir un Timer (par ex. TIM6).
 *    - Configurer le Timer pour une base de temps de 1 microseconde.
 */

#include "bh1750.h"
#include "tim.h" // Assurez-vous que votre fichier tim.h est inclus

// --- Configuration des Périphériques ---
// Définir le Timer utilisé pour les délais en microsecondes.
#define DELAY_TIMER htim6 // Adaptez si vous utilisez un autre Timer

// Définir les ports et broches pour l'I2C logiciel
#define BH1750_SCL_PORT	softI2C1_SCL_GPIO_Port // Port GPIO pour SCL
#define BH1750_SCL_PIN	softI2C1_SCL_Pin       // Broche GPIO pour SCL

#define BH1750_SDA_PORT	softI2C1_SDA_GPIO_Port // Port GPIO pour SDA
#define BH1750_SDA_PIN	softI2C1_SDA_Pin       // Broche GPIO pour SDA
// --- Fin Configuration des Périphériques ---

// --- Macros pour le contrôle des lignes I2C ---
#define BH1750_SCL_HIGH()		HAL_GPIO_WritePin(BH1750_SCL_PORT, BH1750_SCL_PIN, GPIO_PIN_SET)
#define BH1750_SCL_LOW()		HAL_GPIO_WritePin(BH1750_SCL_PORT, BH1750_SCL_PIN, GPIO_PIN_RESET)
#define BH1750_SCL_READ()		HAL_GPIO_ReadPin(BH1750_SCL_PORT, BH1750_SCL_PIN)

#define BH1750_SDA_HIGH()		HAL_GPIO_WritePin(BH1750_SDA_PORT, BH1750_SDA_PIN, GPIO_PIN_SET)
#define BH1750_SDA_LOW()		HAL_GPIO_WritePin(BH1750_SDA_PORT, BH1750_SDA_PIN, GPIO_PIN_RESET)
#define BH1750_SDA_READ()		HAL_GPIO_ReadPin(BH1750_SDA_PORT, BH1750_SDA_PIN)
// --- Fin Macros ---

// Délai en microsecondes utilisant le Timer configuré
void BH1750_Delay_us(uint16_t us)
{
    uint16_t timer_count = 0;
    // Charger une valeur pour obtenir le délai souhaité.
    // La valeur exacte dépend de la fréquence du Timer.
    // Exemple pour une fréquence de 72MHz et un délai de 5us :
    uint16_t load_value = (uint16_t)(us * 72); // Estimation, ajustez selon la config exacte du Timer
    __HAL_TIM_SET_COUNTER(&DELAY_TIMER, 0);
    HAL_TIM_Base_Start(&DELAY_TIMER);
    while (timer_count < load_value) {
        timer_count = __HAL_TIM_GET_COUNTER(&DELAY_TIMER);
    }
    HAL_TIM_Base_Stop(&DELAY_TIMER);
}

// Définir l'état de la ligne SCL
void SW_I2C_SetSCL(uint8_t state) {
    if (state == 1) BH1750_SCL_HIGH();
    else BH1750_SCL_LOW();
    BH1750_Delay_us(5); // Petit délai pour la stabilité
}

// Définir l'état de la ligne SDA
void SW_I2C_SetSDA(uint8_t state) {
    if (state == 1) BH1750_SDA_HIGH();
    else BH1750_SDA_LOW();
    BH1750_Delay_us(5); // Petit délai pour la stabilité
}

// Générer la condition START de l'I2C
void SW_I2C_StartCondition(void) {
    SW_I2C_SetSDA(1);
    SW_I2C_SetSCL(1);
    SW_I2C_SetSDA(0); // SDA passe de HAUT à BAS pendant que SCL est HAUT
    SW_I2C_SetSCL(0);
}

// Générer la condition STOP de l'I2C
void SW_I2C_StopCondition(void) {
    SW_I2C_SetSDA(0);
    SW_I2C_SetSCL(1);
    SW_I2C_SetSDA(1); // SDA passe de BAS à HAUT pendant que SCL est HAUT
}

// Envoyer un octet via l'I2C logiciel
void SW_I2C_SendByte(uint8_t dataByte) {
    SW_I2C_SetSCL(0); // Abaisser SCL pour commencer la transmission du bit
    for (int i = 0; i < 8; ++i) {
        if ((dataByte & 0x80) == 0) {
            SW_I2C_SetSDA(0); // Envoyer le bit de poids fort en premier
        } else {
            SW_I2C_SetSDA(1);
        }
        dataByte <<= 1; // Décaler pour le bit suivant
        SW_I2C_SetSCL(1); // Mettre SCL à HAUT pour que le périphérique lise le bit
        SW_I2C_SetSCL(0); // Mettre SCL à BAS pour préparer le bit suivant
    }
}

// Recevoir un octet via l'I2C logiciel
uint8_t SW_I2C_ReceiveByte(void) {
    uint8_t receivedData = 0x00;
    SW_I2C_SetSDA(1); // Configurer SDA en entrée (pull-up) avant de recevoir
    for (int i = 0; i < 8; ++i) {
        SW_I2C_SetSCL(1); // Mettre SCL à HAUT pour que le périphérique envoie le bit
        if (BH1750_SDA_READ() == 1) {
            receivedData |= (0x80 >> i); // Construire l'octet reçu (du MSB au LSB)
        }
        SW_I2C_SetSCL(0); // Mettre SCL à BAS
    }
    return receivedData;
}

// Envoyer un acquittement (ACK/NACK)
void SW_I2C_SendAck(uint8_t ackState) {
    if (ackState == 0) {
        SW_I2C_SetSDA(0); // Envoyer ACK (bas)
    } else {
        SW_I2C_SetSDA(1); // Envoyer NACK (haut)
    }
    SW_I2C_SetSCL(1); // Mettre SCL à HAUT
    SW_I2C_SetSCL(0); // Mettre SCL à BAS
}

// Recevoir l'acquittement du périphérique
uint8_t SW_I2C_ReceiveAck(void) {
    SW_I2C_SetSDA(1); // Configurer SDA en entrée
    SW_I2C_SetSCL(1); // Mettre SCL à HAUT
    uint8_t ack = BH1750_SDA_READ(); // Lire l'état de l'acquittement
    SW_I2C_SetSCL(0); // Mettre SCL à BAS
    return ack; // 0 pour ACK, 1 pour NACK
}

// Fonction principale pour obtenir la valeur de luminosité
uint8_t getBH1750_Lux(uint16_t* currentLux) {
    uint16_t luxValue = 0;
    uint8_t ackResult;

    // Adresse du BH1750 (0x23 ou 0x46 selon la configuration ADDR)
    // Ici, nous utilisons 0x46, correspondant à l'adresse 0x23 avec le bit R/W à 0.
    uint8_t deviceAddress = 0x46; // 0x23 << 1

    // Commande pour passer en mode mesure continue haute résolution (0x10)
    uint8_t measurementCommand = 0x10;
    // Commande pour initialiser le capteur (peut être omise si déjà configuré)
    uint8_t powerOnCommand = 0x01; // Power On & Clear flag

    // 1. Initialisation et début de mesure
    SW_I2C_StartCondition();
    ackResult = SW_I2C_ReceiveAck(); // Attendre ACK après START (ne devrait pas arriver ici)
    SW_I2C_SendByte(deviceAddress); // Envoyer l'adresse du périphérique + écriture
    ackResult = SW_I2C_ReceiveAck(); // Recevoir ACK du périphérique
    if (ackResult != 0) { // Si NACK, l'appareil n'a pas répondu
        SW_I2C_StopCondition();
        return 0; // Échec
    }

    SW_I2C_SendByte(powerOnCommand); // Envoyer la commande pour allumer le capteur
    ackResult = SW_I2C_ReceiveAck(); // Recevoir ACK
    if (ackResult != 0) {
        SW_I2C_StopCondition();
        return 0; // Échec
    }

    SW_I2C_StopCondition(); // Fin de la séquence d'initialisation

    // 2. Attente avant la première mesure (typiquement 150-200 ms)
    HAL_Delay(200);

    // 3. Lecture des données de luminosité
    SW_I2C_StartCondition();
    SW_I2C_SendByte(deviceAddress + 1); // Adresse du périphérique + lecture (0x47)
    ackResult = SW_I2C_ReceiveAck(); // Recevoir ACK du périphérique
    if (ackResult != 0) {
        SW_I2C_StopCondition();
        return 0; // Échec
    }

    // Lire l'octet de poids fort (High Byte)
    uint8_t highByte = SW_I2C_ReceiveByte();
    SW_I2C_SendAck(0); // Envoyer ACK pour indiquer que nous sommes prêts à recevoir le prochain octet

    // Lire l'octet de poids faible (Low Byte)
    uint8_t lowByte = SW_I2C_ReceiveByte();
    SW_I2C_SendAck(1); // Envoyer NACK pour indiquer que c'est le dernier octet

    SW_I2C_StopCondition(); // Fin de la lecture

    // Combiner les deux octets pour obtenir la valeur de luminosité brute
    luxValue = (highByte << 8) | lowByte;

    *currentLux = luxValue; // Stocker la valeur dans la variable fournie
    return 1; // Succès
}

Étiquettes: STM32 STM32Cube I2C I2C logiciel BH1750

Publié le 24 juin à 17h23