Dans cette partie on se propose de réaliser une horloge numérique « heure : minute : seconde » dont les chiffres seront affichés sur un écran. Dans les articles précédents, nous avons mis en œuvre un afficheur à cristaux liquides (LCD). On va utiliser le même afficheur pour réaliser cette horloge.

 

Schéma du montage

 

schema horloge lcd pic16f877a

 

 

Principe

Pour fabriquer une horloge, on peut utiliser un quartz de 32,768 kHz ; dans ce cas il suffit d’effecteur des divisions de fréquences pour atteindre une base de temps de 1 seconde.

Pour notre part, nous ne disposons que d’un quartz de 4 MHz, pour créer une base de temps. On utilisera le module Timer0 du microcontrôleur, pour réaliser cette opération.

 

Présentation du Timer0

Ce module peut assurer des fonctions de comptage ou de temporisation ; il possède les caractéristiques suivantes :

Un mode Timer ou Compteur 8 bits ;

Un pré diviseur configurable 8 bits ;

Une source d’horloge qui peut sélectionnée soit en externe ou en interne ;

Une source d’interruption sur dépassement de capacité de 0xFFh à 0x00h ;

Sélection du type de front lors du fonctionnement en mode horloge externe.

Il est accessible en lecture et en écriture.

Schéma de principe du Timer0:

 

schema timer0 pic16f877a

 

Ce schéma montre que la source du signal du Timer peut être :

Le choix des modes interne ou externe est réalisé par T0CS (si T0CS = 1 c’est le signal externe ; si T0CS = 0 c’est le signa interne).

Le pré diviseur programmable est capable d’effectuer des pré divisions de fréquences de 1 à 256 selon les valeurs des bits PS2, PS1 et PS0.

Le registre TMR0 contient la valeur de pré positionnement du Timer ; ce registre 8 bits est incrémenté et lorsque son contenu passe de la valeur hexadécimale 0xFF à 0x00, il y a débordement du Timer, et une interruption a lieu.

Tous ces bits sont contenu dans le registre de contrôle OPTION_REG, les bits pour configurer le pré diviseur, la gestion de l’interruption externe INT ainsi que des résistances de « Pull-Up » du Port B.

 

structure option register pic16f877a

 

Le bit7 :                RBPU : bit de validation des résistances de « Pull-Up » actif à l’état bas ;

Le bit6 :                INTEDG : bit de sélection du type de front de l’interruption externe.

                               1 => interruption sur la broche INT active sur front montant ;

                               0 => interruption sur la broche INT active sur front descendant ;

Le bit5 :                T0CS : Sélection du type d’horloge du Timer0.

                               1 => Transition sur la broche T0CKI ;

                               0 => Horloge interne générée sur instruction ;

Le bit4 :                T0SE : type de transition pour incrémenter le Timer0.

                               1 => Incrémentation sur la transition Haut vers Bas sur la broche T0CKI ;

                               0 => Incrémentation sur la transition Bas vers Haut sur la broche T0CKI ;

Le bit3 :                PSA : Le bit d’assignement du pré diviseur.

                               1 => le pré diviseur est assigné au « Chien de garde » WDT ;

                               0 => le pré diviseur est assigné au Timer0 ;

Les bits 2 à 0 :    Permettent de définir l’échelle de division.

 

Bit Value TMR0 Rate WDT Rate
000 1:2 1:1
001 1:4 1:2
010 1:8 1:4
011 1:16 1:8
100 1:32 1:16
101 1:64 1:32
110 1:128 1:64
111 1:256 1:128

 

Programmation

Les commentaires de tous les programmes ont été écrit en anglais pour faire simple.

Pour  utiliser l’horloge interne du Timer0, ainsi que le pré diviseur, les bits T0CS et PSA seront positionnés à 0.

Parmi les bits PS2, PS1, PS0 seul PS0 sera positionné à 1 ; on aura donc une pré division par 4 ; ce qui avec un quartz de 4 MHz, donnera une base temps de 1 ms.

Le programme sera composé de quatre fichiers d’entête et de quatre fichiers source écrit en langage C.

 

Fichiers d’entête :

Le fichier « Includes.h » : contient l’état des bits de configuration, le type d’horloge utilisé et la déclaration des autres fichiers d’entête.

 

/*
 * File:  Includes.h
 * Author: bidj
 * Comments:
 * Revision history: 2 août 2019
 */

#ifndef __INCLUDES_H

#define __INCLUDES_H

#include 

#include "lcd.h"

#include "Timer.h"
#include "Isr.h"

// Define CPU Frequency
// This must be defined, if __delay_ms() or
// __delay_us() functions are used in the code

#define _XTAL_FREQ   4000000 

// Configuration word for PIC16F877

#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config BOREN = ON       // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

#endif

 

 

Le fichier « Isr.h » : déclare l’interruption du Tmer 0

 

/*
 * File:  Isr.h
 * Author: bidj
 * Comments:
 * Revision history: 2 août 2019
 */

#ifndef __ISR_H
#define __ISR_H

 __interrupt(high_priority)  void Isr(void); //interrupt

#endif

 

 

Le fichier  « lcd.h » : définit les broches de connexion de l’afficheur LCD, et les prototypes des fonctions utilisées pour commander l’afficheur.

 

/*
 * File:  lcd.h
 * Author: bidj
 * Comments:
 * Revision history: 2 août 2019
 */

// Function Declarations for Generic Functions.c

#ifndef __LCD
#define __LCD

// Define Pins

#define LCD_RS               RB0     // Enable pin for LCD
#define LCD_E                RB1     // RS pin for LCD
#define LCD_Data_Bus_D4      RB4         // Data bus bit 4
#define LCD_Data_Bus_D5      RB5         // Data bus bit 5
#define LCD_Data_Bus_D6      RB6         // Data bus bit 6
#define LCD_Data_Bus_D7      RB7         // Data bus bit 7

// Define Pins direction registrers

#define LCD_RS_Dir                 TRISB0
#define LCD_E_Dir                   TRISB1
#define LCD_Data_Bus_Dir_D4        TRISB4
#define LCD_Data_Bus_Dir_D5   TRISB5
#define LCD_Data_Bus_Dir_D6  TRISB6
#define LCD_Data_Bus_Dir_D7 TRISB7

// Constants

#define E_Delay       500 

// Function Declarations

void WriteCommandToLCD(unsigned char);
void WriteDataToLCD(char);
void InitLCD(void);
void WriteStringToLCD(const char*);
void ClearLCDScreen(void);
void DisplayTimeToLCD(unsigned int,unsigned int,unsigned int);
void Set_LCD_Cursor(unsigned char,unsigned char);

#endif

 

 

Le fichier «Timer.h » : définit les variables et les prototypes des fonctions utilisées.

 

/*
 * File:   Timer.h
 * Author:  bidj
 * Comments:
 * Revision history: 2 août 2019
 */

#ifndef __TIMER_H
#define __TIMER_H

// Variables

extern unsigned int msCounter;
extern unsigned int secCounter;
extern unsigned int minCounter;
extern unsigned int hrCounter;

// Function declarations

void InitTimer0(void);
void Init1msecTimerInterrupt(void);
void UpdateTimeCounters(void);

#endif

 

 

Le fichier « Isr.c » : décrit la variable de débordement du Timer0.

 

/*
 * File:   Isr.c
 * Author: bidj
 *
 * Created on 2 août 2019, 07:12
 */

#include "Includes.h"

 __interrupt(high_priority)  void Isr(void) //interrupt

{

      if(T0IF)  //If Timer0 Interrupt

      {

            TMR0 = 0x08; // Timer0 should overflow after 250 instructions (250x4 = 1msec)
            T0IF = 0;    // Clear the interrupt
            msCounter++; // 1 msec time has occurred

      }

}

 

 

Le fichier « lcd.c » : configure l’initialisation, la position du curseur, les fonctions d’écriture des chaînes, des nombres, de l’heure au format : « HH :MM :SS ».

 

 

/*
 * File:   lcd.c
 * Author: bidj
 *
 * Created on 2 août 2019, 09:10
 */

#include "Includes.h"

void ToggleEpinOfLCD(void)

{

      LCD_E = 1;                // Give a pulse on E pin
      __delay_us(E_Delay);      // so that LCD can latch the
      LCD_E = 0;                // data from data bus
      __delay_us(E_Delay);   

}

void WriteCommandToLCD(unsigned char Command) 

{

      LCD_RS = 0;                    // It is a command
      PORTB &= 0x0F;                 // Make Data pins zero
      PORTB |= (Command&0xF0);  // Write Upper nibble of data
      ToggleEpinOfLCD();             // Give pulse on E pin    

      PORTB &= 0x0F;                 // Make Data pins zero
      PORTB |= ((Command<<4)&0xF0); // Write Lower nibble of data
      ToggleEpinOfLCD();             // Give pulse on E pin

}

void WriteDataToLCD(char LCDChar) 

{

      LCD_RS = 1;                    // It is data     
      PORTB &= 0x0F;                 // Make Data pins zero
      PORTB |= (LCDChar&0xF0);  // Write Upper nibble of data
      ToggleEpinOfLCD();             // Give pulse on E pin    
      PORTB &= 0x0F;                 // Make Data pins zero
      PORTB |= ((LCDChar<<4)&0xF0); // Write Lower nibble of data
      ToggleEpinOfLCD();             // Give pulse on E pin

}

void InitLCD(void)

{

      // Firstly make all pins output

      LCD_E                          = 0;   // E  = 0
      LCD_RS                   = 0;   // RS = 0
      LCD_Data_Bus_D4         = 0;       // Data bus = 0
      LCD_Data_Bus_D5         = 0;       // Data bus = 0
      LCD_Data_Bus_D6         = 0;       // Data bus = 0
      LCD_Data_Bus_D7         = 0;       // Data bus = 0
      LCD_E_Dir                = 0;   // Make Output
      LCD_RS_Dir               = 0;   // Make Output
      LCD_Data_Bus_Dir_D4  = 0;   // Make Output
      LCD_Data_Bus_Dir_D5  = 0;   // Make Output
      LCD_Data_Bus_Dir_D6  = 0;   // Make Output
      LCD_Data_Bus_Dir_D7  = 0;   // Make Output

  //========== Reset process from datasheet ===================

   __delay_ms(40);
 
      PORTB &= 0x0F;                 // Make Data pins zero
      PORTB |= 0x03;                 // Write 0x3 value on data bus
      ToggleEpinOfLCD();             // Give pulse on E pin

   __delay_ms(6);

      PORTB &= 0x0F;                 // Make Data pins zero
      PORTB |= 0x03;                 // Write 0x3 value on data bus
      ToggleEpinOfLCD();             // Give pulse on E pin

   __delay_us(300);

      PORTB &= 0x0F;                 // Make Data pins zero
      PORTB |= 0x03;                 // Write 0x3 value on data bus
      ToggleEpinOfLCD();             // Give pulse on E pin

   __delay_ms(2);

      PORTB &= 0x0F;                 // Make Data pins zero
      PORTB |= 0x02;                 // Write 0x20 value on data bus
      ToggleEpinOfLCD();             // Give pulse on E pin    

      __delay_ms(2);

 //============= Reset Process End ======================

      WriteCommandToLCD(0x02);
      WriteCommandToLCD(0x08);
      WriteCommandToLCD(0x00);
      WriteCommandToLCD(0x0C);    //display on,cursor off,blink off
      WriteCommandToLCD(0x00);
      WriteCommandToLCD(0x06);    //entry mode, set increment
 

}

void WriteStringToLCD(const char *s)

{

      while(*s)

            WriteDataToLCD(*s++);   // print first character on LCD

}

void ClearLCDScreen(void)       // Clear the Screen and return cursor to zero position

{

      WriteCommandToLCD(0x01);    // Clear the screen
      __delay_ms(2);              // Delay for cursor to return at zero position

}

//Display time in HH:MM:SS format

void DisplayTimeToLCD( unsigned int h, unsigned int m, unsigned int s )  

{

      ClearLCDScreen();      // Move cursor to zero location and clear screen
      Set_LCD_Cursor(1,1);   //
      WriteStringToLCD("Time: ");

      // Display Hour

      WriteDataToLCD( (h/10)+0x30 );
      WriteDataToLCD( (h%10)+0x30 );

      //Display ':'

      WriteDataToLCD(':');

      //Display Minutes

      WriteDataToLCD( (m/10)+0x30 );
      WriteDataToLCD( (m%10)+0x30 );

      //Display ':'

      WriteDataToLCD(':');

      //Display Seconds

      WriteDataToLCD( (s/10)+0x30 );
      WriteDataToLCD( (s%10)+0x30 );

}

//Set lcd cursor position

void Set_LCD_Cursor(unsigned char a, unsigned char b)

{

      char temp; //,z,y;

      if(a == 1)

      {

          temp = 0x80 + b - 1;
          WriteCommandToLCD(temp); //Lcd_Cmd(y);

      }

      else if(a == 2)

      {

            temp = 0xC0 + b - 1;
            WriteCommandToLCD(temp); //Lcd_Cmd(y);

      }

}

 

 

Le fichier « Timer.c » : il met à jour le Timer0, après le débordement, puis convertit les données de comptage en heurs, minutes et secondes.

 

/*
 * File:   Timer.c
 * Author: bidj
 *
 * Created on 2 août 2019, 10:01
 */

#include "Includes.h"

// define digital clock variables
unsigned int msCounter = 0;
unsigned int secCounter = 0;
unsigned int minCounter = 0;
unsigned int hrCounter = 0;
void InitTimer0(void)

{
      // Timer0 is 8bit timer, select T0CS and PSA to be zero

      OPTION_REG &= 0xC1;     // Make Prescalar 1:4 ou 0xC7 for 1:256
      T0IE = 1;                    // Enable Timer0 interrupt
      GIE = 1;                     // Enable global interrupts
}

void Init1msecTimerInterrupt(void)

{
      InitTimer0();           // Intialize timer0 to genrate 1msec interrupts
}

void UpdateTimeCounters(void)

{

      if (msCounter==1000)

      {
            secCounter++;
            msCounter=0;
      }

      if(secCounter==60)

      {
            minCounter++;
            secCounter=0;
      }

      if(minCounter==60)

      {
            hrCounter++;
            minCounter=0;
      }

      if(hrCounter==24)

      {
            hrCounter = 0;
      }

}

 

 

 

Le fichier « main.c » : c’est le programme principal, il effectue l’affichage de l’heure dans une boucle infinie.

 

/*
 * File:   main.c
 * Author: bidj
 *
 * Created on 2 août 2019, 11:19
 */

#include "Includes.h"

// Main Function

void main(void)

{

      InitLCD();                         // Initialize LCD in 4bit mode
      WriteStringToLCD("Time:");
      Init1msecTimerInterrupt();         // Start 1 msec timer

      while(1)

      {

            if( msCounter == 0 )        // msCounter becomes zero after exact one sec

            {
            // Displays time in HH:MM:SS format
                  DisplayTimeToLCD(hrCounter, minCounter, secCounter);  
            }

            UpdateTimeCounters();       // Update sec, min, hours counters

      }

}

 

 

 

Simulation

Après avoir configuré MPLAB, pour un fonctionnement avec Proteus, lorsqu’on clique sur l’icône « Debug Project », si la compilation se passe sans problèmes, Proteus s’ouvre automatiquement, et on obtient le résultat ci-après :

 

affichage horloge lcd 16f877a

 

Réalisation

Le montage sera câblé sur plaque à essais conformément au schéma ci-dessus. On dispose d’un PIC16F877A, d’un quartz de 4 MHz, de deux condensateurs de 15 pF, d’un afficheur LCD deux lignes 16 caractères, et d’une alimentation +5 V.

Après avoir généré le fichier « .hex », celui-ci est téléchargé sur le microcontrôleur ; voici ce nous obtenons à la mise sous tension :

 

horloge lcd 2

 

On peut imaginer une amélioration de ce montage en ajoutant des boutons de mise à l’heure de l’horloge.