Conception d'une horloge numérique sur STM32 avec commande par quatre boutons

1. Architecture générale du système

1.1 Spécifications fonctionnelles

  • Affichage temporel : heures, minutes, secondes (format 24h)
  • Affichage de la date : année, mois, jour, jour de la semaine
  • Enterface utilisateur : 4 boutons indépendants pour le réglage et la navigation
  • Fonction réveil : programmation d'une alarme
  • Affcihage visuel : écran LCD1602, LCD12864 ou OLED

1.2 Diagramme bloc fonctionnel


┌─────────────────────────────────────────────────────────────┐
│           Système Horloge Numérique STM32                   │
├─────────────────────────────────────────────────────────────┤
│  Contrôleur STM32 │ Module      │ Module       │ Module     │
│  (Unité principale)│ d'entrée    │ d'affichage  │ d'horloge  │
│                   │             │              │            │
│  • Logique        │ • 4 Boutons │ • LCD1602    │ • DS1302   │
│    temporelle     │ • Anti-rebond│ • LCD12864   │ • DS3231   │
│  • Gestion alarme │ • Appui long│ • OLED       │ • RTC      │
│  • Rafraîchissement│ / court    │              │            │
│    affichage      │             │              │            │
└─────────────────────────────────────────────────────────────┘

2. Conception du matériel

2.1 Schéma de connexion matériel

Liste des composants principaux
Composant Modèle Quantité Fonction
Microcontrôleur STM32F103C8T6 1 Contrôle central
Écran d'affichage LCD1602 1 Affichage temps/date
Horloge temps réel DS1302 1 Horloge précise
Boutons poussoirs Interrupteurs tactiles 4 Entrées utilisateur
Buzzer Buzzer actif 1 Signalisation alarme
Quartz 32.768 kHz 1 Référence temporelle
Table de connexion des broches

/******************** Définitions des connexions matérielles ********************/
// Broches GPIO pour les boutons
#define BTN_PORT        GPIOA
#define BTN_MODE_PIN    GPIO_Pin_0    // Bouton 1 : changement de mode
#define BTN_INC_PIN     GPIO_Pin_1    // Bouton 2 : incrément
#define BTN_DEC_PIN     GPIO_Pin_2    // Bouton 3 : décrément
#define BTN_OK_PIN      GPIO_Pin_3    // Bouton 4 : validation/annulation

// Broches pour le LCD1602
#define LCD_REG_SELECT  GPIO_Pin_8
#define LCD_READ_WRITE  GPIO_Pin_9
#define LCD_ENABLE      GPIO_Pin_10
#define LCD_DATA_BIT4   GPIO_Pin_11
#define LCD_DATA_BIT5   GPIO_Pin_12
#define LCD_DATA_BIT6   GPIO_Pin_13
#define LCD_DATA_BIT7   GPIO_Pin_14

// Broches pour le DS1302
#define RTC_CHIP_SELECT GPIO_Pin_4
#define RTC_DATA_LINE   GPIO_Pin_5
#define RTC_CLOCK_LINE  GPIO_Pin_6

// Broche du buzzer
#define ALARM_BUZZER    GPIO_Pin_7

3. Conception logicielle

3.1 Définition des fonctionnalités des boutons


/******************** Énumérations pour les boutons et les modes ********************/
typedef enum {
    BTN_FUNCTION_MODE = 0,    // Fonction : changement de mode
    BTN_FUNCTION_INC,         // Fonction : incrémentation
    BTN_FUNCTION_DEC,         // Fonction : décrémentation
    BTN_FUNCTION_OK           // Fonction : validation
} ButtonFunction;

// Modes d'opération du système
typedef enum {
    SYS_MODE_VIEW = 0,        // Mode visualisation
    SYS_MODE_SET_TIME,        // Mode réglage de l'heure
    SYS_MODE_SET_DATE,        // Mode réglage de la date
    SYS_MODE_SET_ALARM,       // Mode réglage de l'alarme
    SYS_MODE_ALARM_ACTIVE     // Mode alarme active
} SystemMode;

// États pendant le réglage
typedef enum {
    ADJUST_HOUR = 0,          // Ajustement de l'heure
    ADJUST_MINUTE,            // Ajustement des minutes
    ADJUST_SECOND,            // Ajustement des secondes
    ADJUST_YEAR,              // Ajustement de l'année
    ADJUST_MONTH,             // Ajustement du mois
    ADJUST_DAY,               // Ajustement du jour
    ADJUST_ALARM_HOUR,        // Ajustement de l'heure de l'alarme
    ADJUST_ALARM_MINUTE       // Ajustement des minutes de l'alarme
} AdjustmentState;

3.2 Structures de données principales


/******************** Structure pour le temps ********************/
typedef struct {
    uint8_t current_hour;     // Heure (0-23)
    uint8_t current_minute;   // Minute (0-59)
    uint8_t current_second;   // Seconde (0-59)
    uint8_t current_year;     // Année (00-99)
    uint8_t current_month;    // Mois (1-12)
    uint8_t current_day;      // Jour (1-31)
    uint8_t current_weekday;  // Jour de la semaine (1-7)
} TimeData;

typedef struct {
    uint8_t alarm_hour;       // Heure de l'alarme
    uint8_t alarm_minute;     // Minute de l'alarme
    uint8_t alarm_enabled;    // Activation de l'alarme
} AlarmData;

// Variables globales du système
TimeData g_current_time;
AlarmData g_alarm_config;
SystemMode g_system_state = SYS_MODE_VIEW;
AdjustmentState g_adjust_target = ADJUST_HOUR;
uint8_t g_blink_state = 0;           // État du clignotement
uint8_t g_alarm_triggered = 0;       // Drapeau d'alarme déclenchée

3.3 Gestion des boutons avec anti-rebond


/**
 * @file button_handler.c
 * @brief Gestion des boutons avec traitement anti-rebond
 */

#include "stm32f10x.h"
#include "button_handler.h"

#define DEBOUNCE_DELAY_MS    10      // Délai anti-rebond
#define LONG_PRESS_THRESHOLD 1000    // Seuil pour appui long

static uint8_t button_states[4] = {1, 1, 1, 1};
static uint16_t press_durations[4] = {0};

/**
 * @brief Initialisation des broches des boutons
 */
void Buttons_Init(void) {
    GPIO_InitTypeDef gpio_config;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    gpio_config.GPIO_Pin = BTN_MODE_PIN | BTN_INC_PIN | BTN_DEC_PIN | BTN_OK_PIN;
    gpio_config.GPIO_Mode = GPIO_Mode_IPU;
    gpio_config.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(BTN_PORT, &gpio_config);
}

/**
 * @brief Détection d'appui simple
 * @return Code du bouton pressé, 0 si aucun
 */
uint8_t Detect_Button_Press(void) {
    uint8_t pressed_button = 0;
    
    // Vérification des différents boutons
    if (GPIO_ReadInputDataBit(BTN_PORT, BTN_MODE_PIN) == RESET) {
        Delay_Ms(DEBOUNCE_DELAY_MS);
        if (GPIO_ReadInputDataBit(BTN_PORT, BTN_MODE_PIN) == RESET) {
            while (GPIO_ReadInputDataBit(BTN_PORT, BTN_MODE_PIN) == RESET);
            pressed_button = BTN_FUNCTION_MODE;
        }
    }
    else if (GPIO_ReadInputDataBit(BTN_PORT, BTN_INC_PIN) == RESET) {
        Delay_Ms(DEBOUNCE_DELAY_MS);
        if (GPIO_ReadInputDataBit(BTN_PORT, BTN_INC_PIN) == RESET) {
            while (GPIO_ReadInputDataBit(BTN_PORT, BTN_INC_PIN) == RESET);
            pressed_button = BTN_FUNCTION_INC;
        }
    }
    else if (GPIO_ReadInputDataBit(BTN_PORT, BTN_DEC_PIN) == RESET) {
        Delay_Ms(DEBOUNCE_DELAY_MS);
        if (GPIO_ReadInputDataBit(BTN_PORT, BTN_DEC_PIN) == RESET) {
            while (GPIO_ReadInputDataBit(BTN_PORT, BTN_DEC_PIN) == RESET);
            pressed_button = BTN_FUNCTION_DEC;
        }
    }
    else if (GPIO_ReadInputDataBit(BTN_PORT, BTN_OK_PIN) == RESET) {
        Delay_Ms(DEBOUNCE_DELAY_MS);
        if (GPIO_ReadInputDataBit(BTN_PORT, BTN_OK_PIN) == RESET) {
            while (GPIO_ReadInputDataBit(BTN_PORT, BTN_OK_PIN) == RESET);
            pressed_button = BTN_FUNCTION_OK;
        }
    }
    
    return pressed_button;
}

/**
 * @brief Détection d'appui avec gestion court/long
 * @param press_type Pointeur pour stocker le type d'appui (1: court, 2: long)
 * @return Code du bouton détecté
 */
uint8_t Scan_Button_Press(uint8_t *press_type) {
    uint8_t detected_btn = 0;
    uint8_t btn_index;
    static uint8_t previous_states[4] = {1, 1, 1, 1};
    uint8_t current_states[4];
    uint8_t btn_pins[4] = {BTN_MODE_PIN, BTN_INC_PIN, BTN_DEC_PIN, BTN_OK_PIN};
    
    *press_type = 0;
    
    for (btn_index = 0; btn_index < 4; btn_index++) {
        current_states[btn_index] = GPIO_ReadInputDataBit(BTN_PORT, btn_pins[btn_index]);
        
        // Début d'appui détecté
        if (previous_states[btn_index] && !current_states[btn_index]) {
            Delay_Ms(DEBOUNCE_DELAY_MS);
            press_durations[btn_index] = 0;
        }
        // Appui maintenu
        else if (!previous_states[btn_index] && !current_states[btn_index]) {
            press_durations[btn_index]++;
            if (press_durations[btn_index] >= (LONG_PRESS_THRESHOLD / DEBOUNCE_DELAY_MS)) {
                *press_type = 2;
                detected_btn = btn_index + 1;
                press_durations[btn_index] = 0;
                break;
            }
        }
        // Fin d'appui
        else if (!previous_states[btn_index] && current_states[btn_index]) {
            if (press_durations[btn_index] < (LONG_PRESS_THRESHOLD / DEBOUNCE_DELAY_MS)) {
                *press_type = 1;
                detected_btn = btn_index + 1;
            }
            press_durations[btn_index] = 0;
        }
        
        previous_states[btn_index] = current_states[btn_index];
    }
    
    return detected_btn;
}

3.4 Logique principale de l'horloge


/**
 * @file clock_logic.c
 * @brief Logique centrale de l'horloge numérique
 */

#include "clock_logic.h"
#include "rtc_driver.h"
#include "display_driver.h"

const char *weekdays_str[] = {"Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"};

/**
 * @brief Mise à jour de l'affichage en fonction du mode
 */
void Update_Display_Content(void) {
    char buffer[16];
    
    switch (g_system_state) {
        case SYS_MODE_VIEW:
            // Affichage normal de l'heure et de la date
            sprintf(buffer, "%02d:%02d:%02d", 
                    g_current_time.current_hour, 
                    g_current_time.current_minute, 
                    g_current_time.current_second);
            Display_String_At(0, 0, buffer);
            
            sprintf(buffer, "%02d-%02d-%02d %s", 
                    g_current_time.current_year, 
                    g_current_time.current_month, 
                    g_current_time.current_day,
                    weekdays_str[g_current_time.current_weekday - 1]);
            Display_String_At(0, 1, buffer);
            break;
            
        case SYS_MODE_SET_TIME:
            // Mode réglage de l'heure avec clignotement
            Display_String_At(0, 0, "Regler Heure:");
            if (g_blink_state) {
                switch (g_adjust_target) {
                    case ADJUST_HOUR:
                        sprintf(buffer, "  :%02d:%02d", 
                                g_current_time.current_minute, 
                                g_current_time.current_second);
                        break;
                    case ADJUST_MINUTE:
                        sprintf(buffer, "%02d:  :%02d", 
                                g_current_time.current_hour, 
                                g_current_time.current_second);
                        break;
                    case ADJUST_SECOND:
                        sprintf(buffer, "%02d:%02d:  ", 
                                g_current_time.current_hour, 
                                g_current_time.current_minute);
                        break;
                }
            } else {
                sprintf(buffer, "%02d:%02d:%02d", 
                        g_current_time.current_hour, 
                        g_current_time.current_minute, 
                        g_current_time.current_second);
            }
            Display_String_At(0, 1, buffer);
            break;
            
        case SYS_MODE_SET_DATE:
            // Mode réglage de la date
            Display_String_At(0, 0, "Regler Date:");
            if (g_blink_state) {
                switch (g_adjust_target) {
                    case ADJUST_YEAR:
                        sprintf(buffer, "  -%02d-%02d", 
                                g_current_time.current_month, 
                                g_current_time.current_day);
                        break;
                    case ADJUST_MONTH:
                        sprintf(buffer, "%02d-  -%02d", 
                                g_current_time.current_year, 
                                g_current_time.current_day);
                        break;
                    case ADJUST_DAY:
                        sprintf(buffer, "%02d-%02d-  ", 
                                g_current_time.current_year, 
                                g_current_time.current_month);
                        break;
                }
            } else {
                sprintf(buffer, "%02d-%02d-%02d", 
                        g_current_time.current_year, 
                        g_current_time.current_month, 
                        g_current_time.current_day);
            }
            Display_String_At(0, 1, buffer);
            break;
            
        case SYS_MODE_SET_ALARM:
            // Mode réglage de l'alarme
            Display_String_At(0, 0, "Regler Alarme:");
            if (g_blink_state) {
                switch (g_adjust_target) {
                    case ADJUST_ALARM_HOUR:
                        sprintf(buffer, "  :%02d", g_alarm_config.alarm_minute);
                        break;
                    case ADJUST_ALARM_MINUTE:
                        sprintf(buffer, "%02d:  ", g_alarm_config.alarm_hour);
                        break;
                }
            } else {
                sprintf(buffer, "%02d:%02d", 
                        g_alarm_config.alarm_hour, 
                        g_alarm_config.alarm_minute);
            }
            Display_String_At(0, 1, buffer);
            break;
    }
}

/**
 * @brief Traitement des événements des boutons
 * @param btn_code Code du bouton
 * @param btn_type Type d'appui (1: court, 2: long)
 */
void Process_Button_Event(uint8_t btn_code, uint8_t btn_type) {
    switch (g_system_state) {
        case SYS_MODE_VIEW:
            if (btn_code == BTN_FUNCTION_MODE) {
                if (btn_type == 1) {
                    g_system_state = SYS_MODE_SET_TIME;
                    g_adjust_target = ADJUST_HOUR;
                    Clear_Display();
                } else if (btn_type == 2) {
                    // Bascule l'état de l'alarme
                    g_alarm_config.alarm_enabled = !g_alarm_config.alarm_enabled;
                    Display_String_At(0, 0, "Alarme:");
                    Display_String_At(0, 1, g_alarm_config.alarm_enabled ? "Active " : "Desact.");
                    Delay_Ms(1000);
                }
            }
            break;
            
        case SYS_MODE_SET_TIME:
            switch (btn_code) {
                case BTN_FUNCTION_MODE:
                    // Passage au champ suivant
                    g_adjust_target = (g_adjust_target + 1) % 3;
                    break;
                    
                case BTN_FUNCTION_INC:
                    // Incrémentation de la valeur
                    switch (g_adjust_target) {
                        case ADJUST_HOUR:
                            g_current_time.current_hour = (g_current_time.current_hour + 1) % 24;
                            break;
                        case ADJUST_MINUTE:
                            g_current_time.current_minute = (g_current_time.current_minute + 1) % 60;
                            break;
                        case ADJUST_SECOND:
                            g_current_time.current_second = (g_current_time.current_second + 1) % 60;
                            break;
                    }
                    break;
                    
                case BTN_FUNCTION_DEC:
                    // Décrémentation de la valeur
                    switch (g_adjust_target) {
                        case ADJUST_HOUR:
                            g_current_time.current_hour = (g_current_time.current_hour + 23) % 24;
                            break;
                        case ADJUST_MINUTE:
                            g_current_time.current_minute = (g_current_time.current_minute + 59) % 60;
                            break;
                        case ADJUST_SECOND:
                            g_current_time.current_second = (g_current_time.current_second + 59) % 60;
                            break;
                    }
                    break;
                    
                case BTN_FUNCTION_OK:
                    // Validation et retour au mode visualisation
                    RTC_Write_Time(&g_current_time);
                    g_system_state = SYS_MODE_VIEW;
                    Clear_Display();
                    break;
            }
            break;
            
        case SYS_MODE_SET_DATE:
            switch (btn_code) {
                case BTN_FUNCTION_MODE:
                    g_adjust_target = (g_adjust_target + 1) % 3;
                    break;
                    
                case BTN_FUNCTION_INC:
                    switch (g_adjust_target) {
                        case ADJUST_YEAR:
                            g_current_time.current_year = (g_current_time.current_year + 1) % 100;
                            break;
                        case ADJUST_MONTH:
                            g_current_time.current_month = (g_current_time.current_month % 12) + 1;
                            break;
                        case ADJUST_DAY:
                            g_current_time.current_day = (g_current_time.current_day % 31) + 1;
                            break;
                    }
                    break;
                    
                case BTN_FUNCTION_DEC:
                    switch (g_adjust_target) {
                        case ADJUST_YEAR:
                            g_current_time.current_year = (g_current_time.current_year + 99) % 100;
                            break;
                        case ADJUST_MONTH:
                            g_current_time.current_month = ((g_current_time.current_month + 10) % 12) + 1;
                            break;
                        case ADJUST_DAY:
                            g_current_time.current_day = ((g_current_time.current_day + 30) % 31) + 1;
                            break;
                    }
                    break;
                    
                case BTN_FUNCTION_OK:
                    RTC_Write_Date(&g_current_time);
                    g_system_state = SYS_MODE_VIEW;
                    Clear_Display();
                    break;
            }
            break;
    }
}

/**
 * @brief Vérification du déclenchement de l'alarme
 */
void Check_Alarm_Condition(void) {
    if (g_alarm_config.alarm_enabled && 
        g_current_time.current_hour == g_alarm_config.alarm_hour &&
        g_current_time.current_minute == g_alarm_config.alarm_minute &&
        g_current_time.current_second == 0) {
        g_alarm_triggered = 1;
        g_system_state = SYS_MODE_ALARM_ACTIVE;
        Clear_Display();
        Display_String_At(0, 0, "REVEIL!");
        Display_String_At(0, 1, "Appuyez sur touche");
    }
}

/**
 * @brief Gestion du signal sonore de l'alarme
 */
void Handle_Alarm_Signal(void) {
    static uint16_t signal_counter = 0;
    
    if (g_alarm_triggered) {
        // Génération du signal sonore intermittent
        if (signal_counter < 100) {
            GPIO_SetBits(GPIOB, ALARM_BUZZER);
        } else if (signal_counter < 200) {
            GPIO_ResetBits(GPIOB, ALARM_BUZZER);
        } else {
            signal_counter = 0;
        }
        signal_counter++;
        
        // Arrêt de l'alarme sur pression d'une touche
        if (Detect_Button_Press() != 0) {
            g_alarm_triggered = 0;
            g_system_state = SYS_MODE_VIEW;
            GPIO_ResetBits(GPIOB, ALARM_BUZZER);
            Clear_Display();
        }
    }
}

3.5 Programme principal


/**
 * @file main.c
 * @brief Point d'entrée principal de l'application horloge
 */

#include "stm32f10x.h"
#include "clock_logic.h"
#include "button_handler.h"
#include "rtc_driver.h"
#include "display_driver.h"
#include "timing_utils.h"

volatile uint32_t system_timer_tick = 0;

/**
 * @brief Initialisation globale du système
 */
void Initialize_System(void) {
    System_Clock_Config();
    
    Delay_Timer_Init();
    Buttons_Init();
    Display_Init();
    RTC_Module_Init();
    
    // Chargement de l'heure depuis la puce RTC
    RTC_Read_Time(&g_current_time);
    RTC_Read_Date(&g_current_time);
    
    // Configuration initiale de l'alarme
    g_alarm_config.alarm_hour = 7;
    g_alarm_config.alarm_minute = 30;
    g_alarm_config.alarm_enabled = 1;
    
    // Configuration du buzzer
    GPIO_InitTypeDef buzzer_gpio;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    buzzer_gpio.GPIO_Pin = ALARM_BUZZER;
    buzzer_gpio.GPIO_Mode = GPIO_Mode_Out_PP;
    buzzer_gpio.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &buzzer_gpio);
    GPIO_ResetBits(GPIOB, ALARM_BUZZER);
}

/**
 * @brief Routine d'interruption du timer système
 */
void SysTick_IRQHandler(void) {
    system_timer_tick++;
    
    // Clignotement toutes les 500ms
    if (system_timer_tick % 500 == 0) {
        g_blink_state = !g_blink_state;
    }
}

/**
 * @brief Boucle principale de l'application
 */
int main(void) {
    uint8_t detected_button;
    uint8_t press_type;
    uint32_t last_rtc_read = 0;
    
    Initialize_System();
    
    while (1) {
        // Détection et traitement des boutons
        detected_button = Scan_Button_Press(&press_type);
        if (detected_button != 0) {
            Process_Button_Event(detected_button, press_type);
        }
        
        // Actualisation périodique de l'heure depuis la RTC
        if (g_system_state == SYS_MODE_VIEW) {
            if (system_timer_tick - last_rtc_read >= 1000) {
                RTC_Read_Time(&g_current_time);
                RTC_Read_Date(&g_current_time);
                last_rtc_read = system_timer_tick;
            }
        }
        
        // Rafraîchissement de l'affichage
        Update_Display_Content();
        
        // Surveillance de l'alarme
        Check_Alarm_Condition();
        
        // Gestion du signal d'alarme
        Handle_Alarm_Signal();
        
        Delay_Ms(10);
    }
}

3.6 Pilote pour le circuit DS1302


/**
 * @file rtc_driver.c
 * @brief Pilote pour le circuit d'horloge temps réel DS1302
 */

#include "rtc_driver.h"

// Adresses des registres du DS1302
#define REG_SECONDS      0x80
#define REG_MINUTES      0x82
#define REG_HOURS        0x84
#define REG_DATE         0x86
#define REG_MONTHS       0x88
#define REG_WEEKDAYS     0x8A
#define REG_YEARS        0x8C
#define REG_CONTROL      0x8E

/**
 * @brief Initialisation des broches et configuration de la puce
 */
void RTC_Module_Init(void) {
    GPIO_InitTypeDef rtc_pins;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // Broche de sélection de puce
    rtc_pins.GPIO_Pin = RTC_CHIP_SELECT;
    rtc_pins.GPIO_Mode = GPIO_Mode_Out_PP;
    rtc_pins.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &rtc_pins);
    
    // Broche de données
    rtc_pins.GPIO_Pin = RTC_DATA_LINE;
    GPIO_Init(GPIOA, &rtc_pins);
    
    // Broche d'horloge
    rtc_pins.GPIO_Pin = RTC_CLOCK_LINE;
    GPIO_Init(GPIOA, &rtc_pins);
    
    // Désactivation de la protection en écriture
    RTC_Write_Byte(REG_CONTROL, 0x00);
}

/**
 * @brief Écriture d'un octet dans un registre
 */
void RTC_Write_Byte(uint8_t register_addr, uint8_t data_byte) {
    uint8_t bit_index;
    
    GPIO_SetBits(GPIOA, RTC_CHIP_SELECT);
    
    // Transmission de l'adresse
    for (bit_index = 0; bit_index < 8; bit_index++) {
        GPIO_WriteBit(GPIOA, RTC_DATA_LINE, 
                     (BitAction)((register_addr >> bit_index) & 0x01));
        GPIO_SetBits(GPIOA, RTC_CLOCK_LINE);
        GPIO_ResetBits(GPIOA, RTC_CLOCK_LINE);
    }
    
    // Transmission des données
    for (bit_index = 0; bit_index < 8; bit_index++) {
        GPIO_WriteBit(GPIOA, RTC_DATA_LINE, 
                     (BitAction)((data_byte >> bit_index) & 0x01));
        GPIO_SetBits(GPIOA, RTC_CLOCK_LINE);
        GPIO_ResetBits(GPIOA, RTC_CLOCK_LINE);
    }
    
    GPIO_ResetBits(GPIOA, RTC_CHIP_SELECT);
}

/**
 * @brief Lecture d'un octet depuis un registre
 */
uint8_t RTC_Read_Byte(uint8_t register_addr) {
    uint8_t bit_index, read_value = 0;
    
    GPIO_SetBits(GPIOA, RTC_CHIP_SELECT);
    
    // Transmission de l'adresse en lecture
    for (bit_index = 0; bit_index < 8; bit_index++) {
        GPIO_WriteBit(GPIOA, RTC_DATA_LINE, 
                     (BitAction)((register_addr >> bit_index) & 0x01));
        GPIO_SetBits(GPIOA, RTC_CLOCK_LINE);
        GPIO_ResetBits(GPIOA, RTC_CLOCK_LINE);
    }
    
    // Lecture des données
    for (bit_index = 0; bit_index < 8; bit_index++) {
        GPIO_SetBits(GPIOA, RTC_CLOCK_LINE);
        read_value >>= 1;
        if (GPIO_ReadInputDataBit(GPIOA, RTC_DATA_LINE)) {
            read_value |= 0x80;
        }
        GPIO_ResetBits(GPIOA, RTC_CLOCK_LINE);
    }
    
    GPIO_ResetBits(GPIOA, RTC_CHIP_SELECT);
    return read_value;
}

/**
 * @brief Conversion BCD vers décimal
 */
uint8_t BCD_To_Decimal(uint8_t bcd_value) {
    return ((bcd_value >> 4) * 10) + (bcd_value & 0x0F);
}

/**
 * @brief Conversion décimal vers BCD
 */
uint8_t Decimal_To_BCD(uint8_t decimal_value) {
    return ((decimal_value / 10) << 4) | (decimal_value % 10);
}

/**
 * @brief Écriture de l'heure dans la puce RTC
 */
void RTC_Write_Time(TimeData *time_data) {
    RTC_Write_Byte(REG_SECONDS, Decimal_To_BCD(time_data->current_second));
    RTC_Write_Byte(REG_MINUTES, Decimal_To_BCD(time_data->current_minute));
    RTC_Write_Byte(REG_HOURS, Decimal_To_BCD(time_data->current_hour));
}

/**
 * @brief Lecture de l'heure depuis la puce RTC
 */
void RTC_Read_Time(TimeData *time_data) {
    time_data->current_second = BCD_To_Decimal(RTC_Read_Byte(REG_SECONDS));
    time_data->current_minute = BCD_To_Decimal(RTC_Read_Byte(REG_MINUTES));
    time_data->current_hour = BCD_To_Decimal(RTC_Read_Byte(REG_HOURS));
}

3.7 Pilote d'affichage LCD1602


/**
 * @file display_driver.c
 * @brief Pilote pour l'écran LCD 16x2 en mode 4 bits
 */

#include "display_driver.h"

// Fonctions d'attente temporisées
static void Wait_Microseconds(uint16_t us_count) {
    while (us_count--) {
        __NOP(); __NOP(); __NOP(); __NOP();
    }
}

static void Wait_Milliseconds(uint16_t ms_count) {
    uint16_t i, j;
    for (i = 0; i < ms_count; i++) {
        for (j = 0; j < 1000; j++) {
            __NOP();
        }
    }
}

/**
 * @brief Envoi d'une commande à l'écran
 */
void Send_Command(uint8_t command) {
    GPIO_ResetBits(GPIOB, LCD_REG_SELECT);
    GPIO_ResetBits(GPIOB, LCD_READ_WRITE);
    GPIO_SetBits(GPIOB, LCD_ENABLE);
    
    GPIO_WriteBit(GPIOB, LCD_DATA_BIT4, (BitAction)((command >> 4) & 0x01));
    GPIO_WriteBit(GPIOB, LCD_DATA_BIT5, (BitAction)((command >> 5) & 0x01));
    GPIO_WriteBit(GPIOB, LCD_DATA_BIT6, (BitAction)((command >> 6) & 0x01));
    GPIO_WriteBit(GPIOB, LCD_DATA_BIT7, (BitAction)((command >> 7) & 0x01));
    
    Wait_Microseconds(1);
    GPIO_ResetBits(GPIOB, LCD_ENABLE);
    Wait_Milliseconds(2);
}

/**
 * @brief Envoi d'une donnée à l'écran
 */
void Send_Data(uint8_t data) {
    GPIO_SetBits(GPIOB, LCD_REG_SELECT);
    GPIO_ResetBits(GPIOB, LCD_READ_WRITE);
    GPIO_SetBits(GPIOB, LCD_ENABLE);
    
    GPIO_WriteBit(GPIOB, LCD_DATA_BIT4, (BitAction)((data >> 4) & 0x01));
    GPIO_WriteBit(GPIOB, LCD_DATA_BIT5, (BitAction)((data >> 5) & 0x01));
    GPIO_WriteBit(GPIOB, LCD_DATA_BIT6, (BitAction)((data >> 6) & 0x01));
    GPIO_WriteBit(GPIOB, LCD_DATA_BIT7, (BitAction)((data >> 7) & 0x01));
    
    Wait_Microseconds(1);
    GPIO_ResetBits(GPIOB, LCD_ENABLE);
    Wait_Milliseconds(2);
}

/**
 * @brief Initialisation de l'écran LCD
 */
void Display_Init(void) {
    GPIO_InitTypeDef lcd_pins;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    lcd_pins.GPIO_Pin = LCD_REG_SELECT | LCD_READ_WRITE | LCD_ENABLE |
                       LCD_DATA_BIT4 | LCD_DATA_BIT5 | LCD_DATA_BIT6 | LCD_DATA_BIT7;
    lcd_pins.GPIO_Mode = GPIO_Mode_Out_PP;
    lcd_pins.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &lcd_pins);
    
    Wait_Milliseconds(15);
    
    // Séquence d'initialisation en mode 4 bits
    Send_Command(0x33);  // Initialisation mode 8 bits
    Send_Command(0x32);  // Basculement en mode 4 bits
    Send_Command(0x28);  // Configuration : 4 bits, 2 lignes, fonte 5x7
    Send_Command(0x0C);  // Affichage activé, curseur désactivé
    Send_Command(0x06);  // Mode incrément, pas de décalage
    Send_Command(0x01);  // Effacement de l'écran
    Wait_Milliseconds(2);
}

/**
 * @brief Affichage d'une chaîne à une position donnée
 */
void Display_String_At(uint8_t col, uint8_t row, char *text) {
    uint8_t address;
    
    if (row == 0) {
        address = 0x80 + col;
    } else {
        address = 0xC0 + col;
    }
    
    Send_Command(address);
    
    while (*text) {
        Send_Data(*text++);
    }
}

/**
 * @brief Effacement complet de l'écran
 */
void Clear_Display(void) {
    Send_Command(0x01);
    Wait_Milliseconds(2);
}

4. Pistes d'amélioration et extensions

4.1 Fonctionnalités supplémentaires envisageables

  1. Mesure de température : intégration d'un capteur DS18B20
  2. Calendrier lunaire : ajout d'un algorithme de conversion
  3. Fuseaux horaires : support du temps universel et des décalages
  4. Minuteur : fonction de compte à rebours programmable
  5. Chronomètre : mesure de durées avec précision
  6. Rétroéclairage adaptatif : modulation PWM de la luminosité
  7. Mémoire non-volatile : sauvegarde des paramètres en EEPROM

4.2 Évolutions matérielles possibles

Composant Solution actuelle Solution améliorée Avantages
Écran LCD1602 OLED 128x64 Meilleur contraste, faible consommation
Horloge RTC DS1302 DS3231 Précision supérieure, compensation thermique
Interface tactile Boutons mécaniques Boutons capacitifs Design plus élégant, durabilité accrue
Alimentation Alimentation directe Batterie Li-ion avec gestion Autonomie et portabilité améliorées

4.3 Optimisations logicielles

  1. Gestion de l'énergie : mode veille pendant les périodes d'inactivité
  2. Luminosité automatique : adaptation à l'éclairage ambiant
  3. Synthèse vocale : annonce sonore de l'heure à la demande
  4. Connectivité sans fil : synchronisation via Bluetooth ou WiFi
  5. Synchronisation réseau : récupération du temps via NTP

Étiquettes: STM32 DS1302 LCD1602 Horloge numérique Microcontrôleur

Publié le 9 juillet à 06h28