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
/*
* 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;
}