Un voltmètre ave un PIC16F877

Le projet

Ce petit projet montre comment on peut utiliser le CAN d'un pic pour faire une mesure. Le principe peut être utilisé avec toute sorte de capteur délivrant une tension proportionnelle à la mesure.
Il met en oeuvre le CAN d'un PIC16F677, mesure des tensions sur les entrées analogiques et affiche le résultat sur un écran LCD.
Le programme est écrit en C et avec MPLAB X IDE.

Le Schéma structurel


Les connexions pour le LCD

Pin Symbole Connexion
1
Vss
0 V
2
Vdd
+ 5V
3
Vee
curseur P2
4
RS
RD2
5
R/W
0 V
6
E
RD3
7
DB0
0 V
8
DB1
0 V
9
DB2
0 V
10
DB3
0 V
11
DB4
RD4
12
DB5
RD5
13
DB6
RD6
14
DB7
RD7
15
A led
R1
16
K led
0 V

Un écran LCD est contrôlé en mode 4 bits par le port D du Pic.
Le LCD doit être compatible avec le contrôleur Hitachi HD 44780

Il faut respecter les branchements du tableau ci-contre.

Adapter le brochage au connecteur. Le TC1602C de Winstar utilisé a un brochage un peu exotique.


Le Pic est cadencé à la fréquence de 8 Mhz avec un oscillateur à quartz externe.
2 Leds sont placées sur RB2 et RB1 (rouge et jaune). La LED jaune clignote à chaque conversion. La LED rouge indique un dépassement de la mesure.
Le bouton poussoir S1 permet de sélectionner l'entrée analogique parmi les 8 de AN0 à AN7.
P2 permet de régler le contraste de l'écran LCD.
P1 délivre une tension de 0 à 5 V sur l'entrée analogique AN0, pour tester le fonctionnement.
J1 est le connecteur de programmation et de débogage pour la programmation ICSP.

Le cablâge
Jai utilisé le fer à souder et une plaque pastillée, plus robuste qu'une plaque de connexions. Les liaisons entre les composants sont bien entendu au dos de la plaque côté pastilles.

Voici le résultat en fonctionnement, Pic programmé.


Le programme

Le programme utilise la librarie lcd.h qui permet de placer des caractères sur l'afficheur ou du texte.

Pour l'initialisation de l'ADC ADC_init et la fonction GetADCValue on trouve un tutoriel sur le site ElectroSome qui permet de comprendre les valeurs à placer dans les différents registres.

La fonction sprintf(value,"U=%.3f",ADC_value) stocke dans le tableau value les caractères d'ADC_Value . Le tableau est lu et affiché par Lcd_write_string(). Ce qui permet la conversion en chaine de caractères .

Pour afficher les entrées de 0 à 7, j'utilise la fonction Lcd_write_char(). Le caractère 48 en décimal (code ASCII) est le 0. Il suffit d'ajouter 48 à la variable Ch (Channel) .

L'ensemble du projet

Fichiers MPLAB X

 

 

  /* 
 * File:   main.c
 * Author: JMDefais
 * Pic 16F677A
 * Voltmeter
 * Created on 6 novembre 2025, 14:54
 */

#define _XTAL_FREQ 8000000 // Xtal 8 Mhz

#define RS RD2
#define EN RD3
#define D4 RD4
#define D5 RD5
#define D6 RD6
#define D7 RD7
#define LEDY RB2
#define LEDR RB1
#define BTN RB4

#include < xc.h >
#include < stdio.h >
#include "lcd.h"


// BEGIN CONFIG
#pragma config FOSC = HS // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT enabled)
#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)
#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)
//END CONFIG

void ADC_Init()
{
  ADCON0 = 0x81;                //Turn ON ADC and Clock Selection
  TRISA  = 0x2F;               // Make RA5, RA3, RA2, RA1, RA0 input
  TRISE  = 0x07;               // Make RE0, RE1 and RE2 input
  ADCON1 = 0x80;                //RA0 à RA7 entrées analogiques Vref VDD
}

int GetADCValue(unsigned char Channel)
{
	ADCON0 &= 0xc7;         // Clear Channel selection bits
	ADCON0 |= (Channel<<3); // Select channel pin as ADC input
    
    __delay_ms(10);         // For Acqusition Time 
							// to charge up and show correct value
	GO_nDONE  = 1;		    // Enable Go/Done

	while(GO_nDONE);        // Wait for conversion completion

	return ((ADRESH<<8)+ADRESL);   // Return 10 bit ADC value
}


int main()
{
  TRISB1 = 0;//RB1 as Output PIN
  TRISB2 = 0;//RB2 as Output PIN
  TRISB4 = 1;//RB4 as Input PIN
  TRISD = 0x00;
  
  char value[50];
  float ADC_value = 0;
  float ADC_value_max = 4.98;
  uint8_t Ch = 0;
  
  Lcd_Init();               // Initialize LCD
  Lcd_Clear();
  Lcd_Set_Cursor(1,4);
  Lcd_Write_String("Voltmetre");
  Lcd_Set_Cursor(2,7);
  Lcd_Write_String("Jmd");
  __delay_ms(2000);
  ADC_Init();              // Initialize ADC
  while(1)
    {
    Lcd_Clear();           
    if (BTN == 0)           // Select channel AN0 - AN7 whith Switch
    {
        Ch = Ch + 1;
        if (Ch > 7)
        {
            Ch = 0;
        }
    }
    ADC_value = GetADCValue(Ch); // Read ADC value from RA0 pin
    ADC_value = (5.0/1024.0)*ADC_value; //Change in Volts
    if (ADC_value > ADC_value_max)
    {
        LEDR = 1;
    }
    else
    {
        LEDR = 0;
    }
    Lcd_Set_Cursor(1, 1);
    sprintf(value,"U = %.3f",ADC_value);    
    Lcd_Write_String(value);
    Lcd_Write_String(" Volts");
    Lcd_Set_Cursor(2,1);
    Lcd_Write_String("Channel ");
    Lcd_Write_Char(Ch + 48);
    LEDY = 1;
    __delay_ms(500); // Half second delay before next reading
    LEDY = 0;
  }
    
  return 0;
}

  

Un thermostat

Comme le PIC possède 8 entrées analogiques, il est très facile de créer un thermostat.

Sur l'entrée analogique AN0 je garde mon potentiomètre pour régler la température de consigne.
Sur l'entrée AN1 je mesure la température ambiante à l'aide d'un LM35.

Le LM35 est à brancher avec : GND -> 0 V, + Vs -> +5V , OUT -> AN1.
Le fonctionnement du LM35 est simple, il délivre une tension proportionnelle à la température.
0 °C donne 0 V, 1 ° C -> 0,01 V, le pas est de 0,01 V par °C. Pour 50 °C, il délivre 0,5 V.


J'affiche la température ambiante sur la 1ère ligne de l'afficheur.
Un appui sur le bouton poussoir permet d'accéder au réglage de la température de consigne qui s'affiche
sur la 2 ème ligne de l'afficheur.
Dès que la température de consigne est dépassée, la LED rouge s'allume.
Il est possible de commander un relais à la place de la LED pour activer un système de chauffage par exemple.

Le thermostat créé peut être largement amélioré car :
- il manque une fenêtre d'hystérésis pour éviter le clignotement de la LED au voisinage de la valeur de consigne.
- le potentiomètre règle une valeur de consigne de 0 à 50 °C, mais le pas de 10 mV du LM35 n'est pas adapté au CAN du pic avec une référence à 5 V, la conversion manque de précision car l'échelle est trop grande. Une référence externe de 1 V permettrai une mesure plus précise jusqu'à 100 °C. Dans ce cas, il ne faut plus utiliser le potentiomètre pour le réglage car il est relié au 5 V.

 


Voici un exemple de programme

L'ensemble du projet

Fichiers MPLAB X

 

 

 
/* 
 * File:   main.c
 * Author: JMDefais
 * Pic 16F677A
 * Thermostat
 * Created on 5 novembre 2025, 14:54
 */

#define _XTAL_FREQ 8000000 // Xtal 8 Mhz

#define RS RD2
#define EN RD3
#define D4 RD4
#define D5 RD5
#define D6 RD6
#define D7 RD7
#define LEDY RB2
#define LEDR RB1
#define BTN RB4

#include < xc.h >
#include < stdio.h >
#include "lcd.h"


// BEGIN CONFIG
#pragma config FOSC = HS // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT enabled)
#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)
#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)
//END CONFIG

void ADC_Init()
{
  ADCON0 = 0x81;                //Turn ON ADC and Clock Selection
  TRISA  = 0x2F;               // Make RA5, RA3, RA2, RA1, RA0 input
  TRISE  = 0x07;               // Make RE0, RE1 and RE2 input
  ADCON1 = 0x80;                //RA0 à RA7 entrées analogiques Vref 5V
}

int GetADCValue(unsigned char Channel)
{
	ADCON0 &= 0xc7;         // Clear Channel selection bits
	ADCON0 |= (Channel<<3); // Select channel pin as ADC input
    
    __delay_ms(10);         // For Acquisition Time 
							// to charge up and show correct value
	GO_nDONE  = 1;		    // Enable Go/Done

	while(GO_nDONE);        // Wait for conversion completion

	return ((ADRESH<<8)+ADRESL);   // Return 10 bit ADC value
}


int main()
{
  TRISB1 = 0;//RB1 as Output PIN
  TRISB2 = 0;//RB2 as Output PIN
  TRISB4 = 1;//RB4 as Input PIN
  TRISD = 0x00;
  
  char value1[40];
  char value2[40];
  float ADC_value1 = 0;
  float ADC_value2 = 0;
  LEDY = 0;
  LEDR = 0;
  
  
  Lcd_Init();               // Initialize LCD
  Lcd_Clear();
  Lcd_Set_Cursor(1,4);
  Lcd_Write_String("Thermostat");
  Lcd_Set_Cursor(2,7);
  Lcd_Write_String("Jmd");
  __delay_ms(2000);
  ADC_Init();              // Initialize ADC
  while(1)
    {
    Lcd_Clear();
 
    ADC_value1 = GetADCValue(1); // Read ADC value from RA1 pin
    ADC_value1 = (500/1024.0)*ADC_value1; //Change in °C   
    Lcd_Set_Cursor(1,1);
    sprintf(value1,"T = %.1f ",ADC_value1);    
    Lcd_Write_String(value1);
    Lcd_Write_Char(42);
    Lcd_Write_String("C");
    
    if (BTN == 0)           // Select value screen
    {
        Lcd_Set_Cursor(2,1);  
        Lcd_Write_String("T cons:");
        ADC_value2 = GetADCValue(0); // Read ADC value from RA0 pin
        ADC_value2 = (50/1024.0)*ADC_value2; //Change in °C   
        sprintf(value2," %.1f ",ADC_value2);    
        Lcd_Write_String(value2);
        Lcd_Write_Char(42);
        Lcd_Write_String("C  ");
    }
    if (BTN == 1)
        Lcd_Set_Cursor(2,1);
        Lcd_Write_String("Bouton Tconsigne");
    {
        
    }
    if (ADC_value1 > ADC_value2)
    {
        LEDR = 1;
    }           
    else
    {
        LEDR = 0;
    }               
    
    __delay_ms(500); // Half second delay before next reading
  }
    
  return 0;
}