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
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:
Ce schéma montre que la source du signal du Timer peut être :
- Soit externe par la broche T0CKI ; ce signal sur T0CKI est actif sur front montant ou descendant à l’aide de la commande T0SE
- Soit interne Fosc/4.
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.
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, 1, et 0 : Permettent de définir l’échelle de division (voir table ci-dessous).
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 :
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 :
On peut imaginer une amélioration de ce montage en ajoutant des boutons de mise à l’heure de l’horloge.