Utiliser les PIC 16F et 18F/Exercices/Le Timer0 des 16FXXX et le langage C
Exercice 1
modifierunsigned int A;
unsigned int Q; /* the quotient */
Q = ((A >> 1) + A) >> 1; /* Q = A*0.11 */
Q = ((Q >> 4) + Q) ; /* Q = A*0.110011 */
Q = ((Q >> 8) + Q) >> 3; /* Q = A*0.00011001100110011 */
/* either Q = A/10 or Q+1 = A/10 for all A < 534,890 */
1°) Sans chercher à comprendre l'algorithme de division ci-dessus, on vous demande de le transformer en une fonction de prototype "unsigned int div10(unsigned int A);"
2°) Écrire un programme complet qui mesure le temps d'exécution du sous programme de division par 10, puis modifier le programme pour qu’il puisse comparer avec une division par 10 normale.
1°)
unsigned int div10(unsigned int A){
unsigned int Q; /* the quotient */
Q = ((A >> 1) + A) >> 1; /* Q = A*0.11 */
Q = ((Q >> 4) + Q) ; /* Q = A*0.110011 */
Q = ((Q >> 8) + Q) >> 3; /* Q = A*0.00011001100110011 */
/* either Q = A/10 or Q+1 = A/10 for all A < 534,890 */
return Q; // ne pas oublier le return pour une fonction
}
2°)
//***** Compilateur Hitech C ************
#include <htc.h> // a changer si autre compilateur
unsigned int div10(unsigned int A);
void main(void){
unsigned int res,i;
unsigned char temps;
// initialisation du timer
OPTION = 0x07; // prescaler 256 , entrée sur quartz
TMR0 = 0x00;
// algorithme de calcul ici plusieurs fois
for(i=0;i<50;i++) {
res=div10(i+15558);
}
// Lecture du timer
temps=TMR0;
TRISB = 0x00; // PORTB en sortie
PORTB = temps;
while(1);
}
unsigned int div10(unsigned int A){
unsigned int Q; /* the quotient */
Q = ((A >> 1) + A) >> 1; /* Q = A*0.11 */
Q = ((Q >> 4) + Q) ; /* Q = A*0.110011 */
Q = ((Q >> 8) + Q) >> 3; /* Q = A*0.00011001100110011 */
/* either Q = A/10 or Q+1 = A/10 for all A < 534,890 */
return Q; // ne pas oublier le return pour une fonction
}
Pour comparer avec l'algorithme classique, remplacer
// algorithme de calcul ici plusieurs fois
for(i=0;i<50;i++) {
res=div10(i+15558);
}
par
// algorithme de calcul ici plusieurs fois
for(i=0;i<50;i++) {
res=(i+15558)/10; // division classique
}
À ma très grande surprise un essai sur la carte easyPIC5 et le compilateur MikroC m'ont donné les résultats en commentaire dans le programme ci-dessous :
// version MikroC pour EasyPIC 5 PIC16F887 à {{Unité|8|{{abréviation|MHz|méga-hertz}}}}
unsigned int div10(unsigned int A);
unsigned int cnt;
void interrupt(){
if (INTCON.T0IF) {
cnt++;
INTCON.T0IF=0;
// TMR0=TMR0+96;
}
}
void main(void){
unsigned int res,i,cntlocal;
unsigned char temps;
ANSEL=ANSELH=0;
// initialisation du timer
OPTION_REG = 0x07; // prescaler 256 , entrée sur quartz
cnt=0;
INTCON = 0xA0;
TMR0 = 0x00;
// algorithme de calcul ici plusieurs fois
for(i=0;i<3000;i++) {
res=div10(i+15558); // donne 10000110111
// res = (i+15558)/10; // donne 10001101
}
// Lecture du timer
cntlocal = cnt;
temps=TMR0;
TRISB = 0x00; // PORTB en sortie
PORTB = temps;
TRISA = 0x00;
PORTA = cntlocal;
while(1);
}
unsigned int div10(unsigned int A){
unsigned int Q; /* the quotient */
Q = ((A >> 1) + A) >> 1; /* Q = A*0.11 */
Q = ((Q >> 4) + Q) ; /* Q = A*0.110011 */
Q = ((Q >> 8) + Q) >> 3; /* Q = A*0.00011001100110011 */
/* either Q = A/10 or Q+1 = A/10 for all A < 534,890 */
return Q; // ne pas oublier le return pour une fonction
}
Vous pouvez noter la présence d'une interruption, mais j’ai été tellement surpris que la division par 10 fonctionne plus rapidement que div10 que j’ai voulu tester s'il n'y avait pas un débordement du timer. Cette façon de faire permet d'afficher un nombre sur 16 bits sur les deux ports A et B du PIC.
Autre essai en 2013 avec l'afficheur GLCD :
void my_glcd_init()
{
ANSEL = ANSELH = 0; // AN pins as digital
Glcd_Init (&PORTB , 0, 1, 2, 3, 5, 4, &PORTD);
Glcd_Set_Font(FontSystem5x8, 5, 8, 0x20); // Sélection police de caractères
Glcd_Fill(0x00);
}
unsigned int div10( unsigned int A)
{
unsigned int Q; /* the quotient */
Q = ((A >> 1) + A) >> 1; /* Q = A*0.11 */
Q = ((Q >> 4) + Q) ; /* Q = A *0.110011 */
Q = ((Q >> 8) + Q) >> 3; /* Q = A *0.00011001100110011 */
/* either Q = A/10 or Q+1 = A/10 for all A < 534 ,890 */
return Q;
}
void main()
{
char text [10];
unsigned int res;
unsigned int timer;
OPTION_REG.T0CS=0;
OPTION_REG.PsA=1;
my_glcd_init();
TMR0=0;
//res=div10(171);
res =171;
res = res/10;
timer=TMR0;
sprinti(text , "timer=%d" ,timer );
Glcd_Write_Text(text , 0, 0, 1);
sprinti(text , "r=%d" ,res);
Glcd_Write_Text(text , 0, 10, 1);
sprinti(text , "t=%d us" ,timer/2 );
Glcd_Write_Text(text , 0, 20, 1);
}
t=2 us avec /10 et t= 38 us avec div10 sont affichés !!!!!! À creuser
Exercice 2
modifierQuestion 1
modifierÉcrire en langage C un programme qui fait la même chose que le programme assembleur ci-dessous :
clrf tmr0 ; début du comptage dans 2 cycles
; (voir remarque plus bas)
bcf INTCON , T0IF ; effacement du flag
loop
btfss INTCON , T0IF ; tester si compteur a débordé
goto loop ; non, attendre débordement
xxx ; poursuivre : 256 évènements écoulés
qui initialise le timer0, efface le flag et attend, à l'aide d'une boucle, le positionnement de ce dernier.
//***** Compilateur Hitech C ************
//#include <pic1684.h>
#include <htc.h> // à changer pour autre compilateur
void main(void){
// initialisation du timer
OPTION = 0x07; // prescaler 256 , entrée sur quartz
TMR0 = 0x00; // clrf tmr0 ; début du comptage dans 2 cycles
T0IF = 0; // bcf INTCON , T0IF ; effacement du flag
while(T0IF == 0); // btfss INTCON , T0IF ; tester si compteur a débordé
// xxx ; poursuivre : 256 évènements écoulés
while(1);
}
Question 2
modifierMais vous pourriez vous dire que vous ne désirez pas forcément attendre 256 incrémentations de tmr0. Supposons que vous désiriez attendre 100 incrémentations. Il suffit dans ce cas de placer dans tmr0 une valeur telle que 100 incrémentations plus tard, tmr0 déborde.
Exemple :
; timer0 en mode 8 bits
movlw 256-100 ; charger 256 – 10
movwf tmr0 ; initialiser tmr0
bcf INTCON,T0IF ; effacement du flag
loop
btfss INTCON,T0IF ; tester si compteur a débordé
goto loop ; non, attendre débordement
xxx ; oui, poursuivre : 100 évènements écoulés
Écrire en langage C un programme qui fait la même chose que le programme assembleur ci-dessus : initialise le timer0, efface le flag et attend à l'aide d'une boucle le positionnement de ce dernier. {{Remarque|contenu=Ceci pourrait sembler correct, mais en fait toute modification de TMR0 entraîne un arrêt de comptage de la part de celui-ci correspondant à 2 cycles d’instruction multipliés par la valeur du pré diviseur. Autrement dit, un arrêt correspond toujours à 2 unités TMR0. Il faut donc tenir compte de cette perte, et placer « 256-98 » et non « 256-100 » dans le timer.
//***** Compilateur Hitech C ************
//#include <pic1684.h>
#include <htc.h> // à changer si autre compilateur
void main(void){
// initialisation du timer
OPTION = 0x07; // prescaler 256 , entrée sur quartz
TMR0 = 0x00; // clrf tmr0 ; début du comptage dans 2 cycles
// Il est risqué d'écrire 256 - 98 pour une valeur 8 bits car 256 est sur 9 bits
// Certains compilateurs le gèrent, d'autres non
T0IF = 158; // movwf tmr0 ; initialiser tmr0
while(T0IF == 0); // btfss INTCON , T0IF ; tester si compteur a débordé
// xxx ; poursuivre : 256-98 évènements écoulés
while(1);
}
Question 3
modifierGénérer un signal de fréquence 1 kHz (avec un quartz de 4 MHz). Pour cela :
- calculer la valeur de la pré division
- calculer la valeur de décomptage
- Écrire le programme.
On va en fait générer 2kHz et changer sans arrêt un bit RA0 du PORTA. 1 MHz / 2kHz=500. Si l’on divise par 2 on peut rester en 8 bits avec comme valeur 250-2=248 à compter donc une initialisation à 256-248=8. Ceci n'est qu'une valeur théorique qui dépend du compilateur. Il faut donc faire des essais pour trouver la valeur exacte. Une autre technique pour être quasi indépendant du compilateur c’est de remplacer :
TMR0 = 8;
par
TMR0 = TMR0+8;
qui dépend moins du temps que l’on a mis pour réaliser les instructions.
//***** Compilateur Hitech C ************
//#include <pic1684.h>
#include <htc.h> //A changer si autre compilateur
void main(void){
// initialisation du timer division par 2
OPTION = 0x00; // prescaler 2 , entrée sur quartz
TMR0 = 0x00; // clrf tmr0 ; début du comptage dans 2 cycles
// bit RA0 du PORTA en sortie
TRISA0=0;
while(1) {
TMR0 = TMR0+8; // peut être le mieux
T0IF = 0;
while (T0IF == 0);
RA0 = ! RA0;
}
}
Question 4
modifierMême chose mais il faut que la période diminue progressivement
Période diminue => fréquence augmente.
TMR0 = TMR0+8;
est remplacé par
TMR0 = TMR0+delta
avec unsigned char delta; initialisé à 8 et augmenté progressivement.
Question 5
modifierGénérer un signal de sortie de rapport cyclique 1/4 sur le même principe. Il y a mieux à faire avec les PICs, utiliser le module CCP.
Exercice 3 (pour PIC 18F)
modifierRefaire l'exercice précédent avec le compilateur C18, c'est-à-dire pour le PIC 18F. On rappelle que Pour le PIC 18F le timer est au choix sur 8 ou 16 bits. On gardera ici bien sûr les mêmes valeurs sur 8 bits que l'exercice précédent.
Question 1 :
//******************** Compilateur C18 ***********************
// quartz comme source
T0CONbits.T0CS = 0;
// si 0 pas besoin diviseur suivant
T0CONbits.PSA = 1;
// division par 1
T0CONbits.T0PS0 = 0;
T0CONbits.T0PS1 = 0;
T0CONbits.T0PS2 = 0;
// passage en 8 bits
T0CONbits.T08BIT = 1;
// TMR0H = 0x00; inutile car en 8 bits
TMR0L = 0x00;
T0CONbits.TMR0ON = 1; //C'est parti
while(1) {
INTCONbits.TMR0IF = 0;
while (INTCONbits.TMR0IF == 0);
printf("Overflow \n");
}
Question 2
//******************** Compilateur C18 ***********************
while(1) {
// TMR0H = 0x00; inutile car en 8 bits
TMR0L = 255-100;
INTCONbits.TMR0IF = 0;
while (INTCONbits.TMR0IF == 0);
printf("Overflow \n");
}
Question 3
//******************** Compilateur C18 ***********************
#include <p18f452.h>
#include <stdio.h>
#pragma config WDT = OFF
void main(void) {
// configure USART
SPBRG = 25; // configure la vitesse (BAUD) 9600 N 8 1
TXSTA = 0x24;
RCSTA = 0x90; // active l'USART
// quartz comme source
T0CONbits.T0CS = 0;
// si 0 pas besoin diviseur suivant
T0CONbits.PSA = 1;
// division par 2
T0CONbits.T0PS0 = 0;
T0CONbits.T0PS1 = 0;
T0CONbits.T0PS2 = 1;
// passage en 8 bits
T0CONbits.T08BIT = 1;
T0CONbits.TMR0ON = 1; //C'est parti
// bit RA0 du PORTA en sortie
TRISAbits.TRISA0=0;
while(1) {
// TMR0H = 0x00; inutile car en 8 bits
TMR0L = TMR0L+8; // peut être le mieux
INTCONbits.TMR0IF = 0;
while (INTCONbits.TMR0IF == 0);
PORTAbits.RA0 = PORTAbits.RA0 ^1;
}
}
Question 5
//******************** Compilateur C18 ***********************
#include <p18f452.h>
#include <stdio.h>
#pragma config WDT = OFF
void main(void) {
// configure USART
SPBRG = 25; // configure la vitesse (BAUD) 9600 N 8 1
TXSTA = 0x24;
RCSTA = 0x90; // active l'USART
// quartz comme source
T0CONbits.T0CS = 0;
// si 0 pas besoin diviseur suivant
T0CONbits.PSA = 1;
// division par 2
T0CONbits.T0PS0 = 0;
T0CONbits.T0PS1 = 0;
T0CONbits.T0PS2 = 1;
// passage en 8 bits
T0CONbits.T08BIT = 1;
T0CONbits.TMR0ON = 1; //C'est parti
// bit RA0 du PORTA en sortie
TRISAbits.TRISA0=0;
while(1) { //4kHz=125 125+2=127
TMR0L = TMR0L+127; // peut être le mieux
INTCONbits.TMR0IF = 0;
while (INTCONbits.TMR0IF == 0);
PORTAbits.RA0 = 1;
TMR0L = TMR0L+127; // peut être le mieux
INTCONbits.TMR0IF = 0;
while (INTCONbits.TMR0IF == 0);
PORTAbits.RA0 = 0;
TMR0L = TMR0L+127; // peut être le mieux
INTCONbits.TMR0IF = 0;
while (INTCONbits.TMR0IF == 0);
PORTAbits.RA0 = 0;
TMR0L = TMR0L+127; // peut être le mieux
INTCONbits.TMR0IF = 0;
while (INTCONbits.TMR0IF == 0);
PORTAbits.RA0 = 0;
}
}