Utiliser les PIC 16F et 18F/Exercices/Le mode capture du module CCP (Capture Compare PWM)
Exercice 1 : Utilisation du mode capture pour lire un clavier PS2
modifierRemarque préliminaire : en toute rigueur, il serait préférable de réaliser cette capture avec une interruption. Mais l'interruption correspondante n'a pas encore été documentée.
Présentation du protocole PS2
modifierLe protocole PS2 complet est un mélange de protocole synchrone et asynchrone. Il a été présenté dans le chapitre : Interfaces VGA et PS/2 d'un autre projet.
Seuls les signaux ps2_clk et ps2_data vont nous intéresser. Comme c’est le clavier qui émet les données c’est à lui de fabriquer l'horloge. La fréquence d'horloge est comprise entre 10 kHz et 16 kHz et on va utiliser la partie capture non pas pour mesurer la fréquence de l'horloge (dans un premier temps en tout cas), mais pour détecter les fronts descendants de l'horloge grâce au bit CCP1IF du registre PIR1.
Matériellement les deux signaux intéressants ps2_clk et ps2_data sont reliés au PORTC :
- ps2_clk est sur le bit b2 de PORTC
- ps2_data est sur le bit b1 de PORTC
Question 1
modifierÉcrire le sous-programme "void initKBD(void)" destiné à initialiser le fonctionnement du module CCP pour qu’il intercepte les fronts descendants de PS2_clk, si les données ps2_data sont sont sur le bit b1 du PORTC. Ne pas oublier de mettre les deux bits du PORTC en entrée.
Voici le sous-programme en Hitech C
//********* Hitech C
void initKBD(void) {
// PS2CLK en entree
TRISC2 = 1;
// PS2DATA en entree
TRISC1 = 1;
// clear le flag CCP1IF
CCP1IF = 0;
// detection de fronts descendants
CCP1M3 = CCP1M1 = CCP1M0=0;
CCP1M2=1;
}
Voici le même sous-programme en MikroC
//********* mikroC
void initKBD(void) {
// PS2CLK en entree
TRISC.F2 = 1;
// PS2DATA en entree
TRISC.F1 = 1;
// clear le flag CCP1IF
PIR1.CCP1IF = 0;
// detection de fronts descendants
CCP1CON.CCP1M3 = CCP1CON.CCP1M1 = CCP1CON.CCP1M0=0;
CCP1CON.CCP1M2=1;
}
Question 2
modifierÉcrire le programme principal qui lit les informations à chaque fois qu'un front descendant arrive et construit le bit du scan-code après lecture pour le sortir sur le PORTB (positionné en sortie).
Voici la solution pour le compilateur HitechC
//******* Hitech C **************
#include <hct.h>
#define PS2DATA RC1
#define PS2CLK RC2
void initKBD(void); // voir question 1
void main(void) {
unsigned char i,result,data[11];
// configure USART pour affichage des printf
....
initKBD();
for (i=0;i<11;i++) { // 11 fronts d'horloge
// attente d'un front descendant
while (CCP1IF == 0);
data[i] = PS2DATA;
// clear le flag CCP1IF
CCP1IF = 0;
}
// maintenant on a le temps de mettre en forme
result=0;
for (i=1;i<9;i++)
result+=data[i]<<(i-1);
// resultat sur PORTB comme demandé
TRISB=0;
PORTB=result;
// pour nos simulations sur PORT série
printf("Le scancode est : %d\n",result);
while(1);
}
Voici la même solution pour le compilateur MikroC qui peut être essayée avec la carte easyPIC5
//******* MikroC **************
// valeurs pour carte EasyPic5 :
#define PS2DATA PORTC.F0
#define PS2CLK PORTC.F1
void initKBD(void); // voir question 1
void main(void) {
unsigned char i,result,data[11];
// configure USART pour affichage des printf
....
initKBD();
TRISB=0;
while(1) {
for (i=0;i<11;i++) { // 11 fronts d'horloge
// attente d'un front descendant
while (PIR1.CCP1IF == 0);
data[i] = PS2DATA;
// clear le flag CCP1IF
PIR1.CCP1IF = 0;
} // for (i=0;i<11;i++)
// maintenant on a le temps de mettre en forme
result=0;
for (i=1;i<9;i++)
result+=data[i]<<(i-1);
// resultat sur PORTB comme demandé
PORTB=result;
// pour autres sorties : sur PORT série, ...
....
}// while(1)
}// main
Question 3
modifierUtiliser maintenant complètement le mode capture pour calculer la fréquence moyenne de l'horloge ps2_clk si notre fréquence de quartz est 4 Mhz.
Un quartz 4 Mhz est encore assez rapide pour évaluer la fréquence de 10 kHz sans interruption mais à l'aide d'une capture (donc avec attente du positionnement du bit CCP1IF). Le programme donné ci-après ne fait qu'afficher trois valeurs de mesure. Une simulation me donne 95 (au lieu de 100) pour les trois mesures. Dans un cas réel il serait bon de faire une moyenne sans prendre la première mesure qui est n’importe quoi puisqu'elle dépend de quand arrive le premier front descendant d'horloge.
//******** Hitech C *******
#include <hct.h>
#include <stdio.h>
void initKBD(void);
void initTimer1(void);
void initCaptureTimer1(void);
void main(void) {
unsigned char i;
unsigned int data[11],periode,tmr1;
// configure USART
...
initKBD();
initTimer1();
for (i=0;i<11;i++) { // 11 fronts d'horloge
// attente d'un front descendant
while(CCP1IF == 0);
//initialisation du timer TMR1H en premier
TMR1H = 0x00;
TMR1L = 0x00;
//lecture du registre de capture
data[i]=CCPR1H;
data[i]<<=8;
data[i]=CCPR1L+data[i];
// clear le flag CCP1IF
CCP1IF = 0;
}
// maintenant on a le temps de calculer
printf("La periode est : %d %d %d\n",data[3],data[4],data[5]);
while(1);
}
void initKBD(void) {
// PS2CLK en entree
TRISC2 = 1;
// PS2DATA en entree
TRISC1 = 1;
// clear le flag CCP1IF
CCP1IF = 0;
// detection de fronts descendants
CCP1M3 = CCP1M1 = CCP1M0 = 0;
CCP1M2 = 1;
}
void initTimer1(void){
// division par 1
T1CKPS0 = 0;
T1CKPS1 = 0;
// choix du quartz
TMR1CS = 0;
// en synchronisé car capture
T1SYNC = 0;
//initialisation du timer TMR1H en premier
TMR1H = 0x00;
TMR1L = 0x00;
// mise en route du timer1
TMR1ON=1;
}
Remarque
modifierLe compilateur MikroC possède une librairie de lecture du PS/2.
Prototype | void Ps2_Init(unsigned short *port); |
Description | Initializes port for work with PS/2 keyboard, with default pin settings. Port pin 0 is Data line, and port pin 1 is Clock line. You need to call either Ps2_Init or Ps2_Config before using other routines of PS/2 library. |
Prototype | void Ps2_Config(char *port, char clock, char data); |
Description | Initializes port for work with PS/2 keyboard, with custom pin settings. Parameters data and clock specify pins of port for Data line and Clock line, respectively. Data and clock need to be in range 0..7 and cannot point to the same pin. You need to call either Ps2_Init or Ps2_Config before using other routines of PS/2 library. |
Prototype | char Ps2_Key_Read(char *value, char *special, char *pressed); |
Description | The function retrieves information about key pressed. Parameter value holds the value of the key pressed. For characters, numerals, punctuation marks, and space, value will store the appropriate ASCII value. Routine “recognizes” the function of Shift and Caps Lock, and behaves appropriately. Parameter special is a flag for special function keys (F1, Enter, Esc, etc). If key pressed is one of these, special will be set to 1, otherwise 0.
Parameter pressed is set to 1 if the key is pressed, and 0 if released. |
Voici un exemple trouvé dans sa documentation :
unsigned short keydata, special, down;
void main() {
CMCON = 0x07; // Disable analog comparators (comment this for PIC18)
INTCON = 0; // Disable all interrupts
Ps2_Init(&PORTA); // Init PS/2 Keyboard on PORTA
Delay_ms(100); // Wait for keyboard to finish
do {
if (Ps2_Key_Read(&keydata, &special, &down)) {
if (down && (keydata == 16)) {// Backspace
// ...do something with a backspace...
}
else if (down && (keydata == 13)) {// Enter
Usart_Write(13);
}
else if (down && !special && keydata) {
Usart_Write(keydata);
}
}
Delay_ms(10); // debounce
} while (1);
}//~!
Exercice 2 (C18)
modifierIdem à exercice 1 mais avec les PICs 18FXXXX.
questions 1-2) Le programme suivant m'a donné un résultat correct (en simulation) avec une horloge a 20 Mhz (et même à 4 Mhz) et une PS2Clock à 10 kHz. Il n'y a donc pas de problème de rapidité car la construction du scancode n’est pas faite au fur et à mesure mais après la saisie des bits dans un tableau.
#include <p18f452.h>
#include <stdio.h>
#pragma config WDT = OFF
#define PS2DATA PORTCbits.RC1
#define PS2CLK PORTCbits.RC2
void initKBD(void);
void main(void) {
unsigned char i,result,data[11];
// configure USART
SPBRG = 25; // configure la vitesse (BAUD) 9600 N 8 1
TXSTA = 0x24;
RCSTA = 0x90; // active l'USART
initKBD();
for (i=0;i<11;i++) { // 11 fronts d'horloge
// attente d'un front descendant
while( PIR1bits.CCP1IF==0);
data[i]=PS2DATA;
// clear le flag CCP1IF
PIR1bits.CCP1IF=0;
}
// maintenant on a le temps de mettre en forme
result=0;
for (i=1;i<9;i++)
result+=data[i]<<(i-1);
// resultat sur PORTB comme demandé
TRISB=0;
PORTB=result;
// pour nos simulations sur PORT série
printf("Le scancode est : %d\n",result);
while(1);
}
void initKBD(void) {
// PS2CLK en entree
TRISCbits.TRISC2=1;
// PS2DATA en entree
TRISCbits.TRISC1=1;
// clear le flag CCP1IF
PIR1bits.CCP1IF=0;
// detection de fronts descendants
CCP1CONbits.CCP1M3=CCP1CONbits.CCP1M1=CCP1CONbits.CCP1M0=0;
CCP1CONbits.CCP1M2=1;
}
Question 3
Un quartz 4 Mhz est encore assez rapide pour évaluer la fréquence de 10 kHz sans interruption mais à l'aide d'une capture (donc avec attente du positionnement du bit CCP1IF). Le programme donné ci-après ne fait qu'afficher trois valeurs de mesure. Une simulation me donne 95 (au lieu de 100) pour les trois mesures. Dans un cas réel il serait bon de faire une moyenne sans prendre la première mesure qui est n’importe quoi puisqu'elle dépend de quand arrive le premier front descendant d'horloge.
#include <p18f452.h>
#include <stdio.h>
#pragma config WDT = OFF
void initKBD(void);
void initTimer1(void);
void initCaptureTimer1(void);
void main(void) {
unsigned char i;
unsigned int data[11],periode,tmr1;
// configure USART
SPBRG = 25; // configure la vitesse (BAUD) 9600 N 8 1
TXSTA = 0x24;
RCSTA = 0x90; // active l'USART
initKBD();
initTimer1();
initCaptureTimer1();
for (i=0;i<11;i++) { // 11 fronts d'horloge
// attente d'un front descendant
while( PIR1bits.CCP1IF==0);
//initialisation du timer TMR1H en premier
TMR1H = 0x00;
TMR1L = 0x00;
//lecture du registre de capture
data[i]=CCPR1H;
data[i]<<=8;
data[i]=CCPR1L+data[i];
// clear le flag CCP1IF
PIR1bits.CCP1IF=0;
}
// maintenant on a le temps de calculer
printf("La periode est : %d %d %d\n",data[3],data[4],data[5]);
while(1);
}
void initKBD(void) {
// PS2CLK en entree
TRISCbits.TRISC2=1;
// PS2DATA en entree
TRISCbits.TRISC1=1;
// clear le flag CCP1IF
PIR1bits.CCP1IF=0;
// detection de fronts descendants
CCP1CONbits.CCP1M3=CCP1CONbits.CCP1M1=CCP1CONbits.CCP1M0=0;
CCP1CONbits.CCP1M2=1;
}
void initTimer1(void){
// division par 1
T1CONbits.T1CKPS0=0;
T1CONbits.T1CKPS1=0;
// choix du quartz
T1CONbits.TMR1CS=0;
// en synchronisé car capture
T1CONbits.NOT_T1SYNC=0;
//initialisation du timer TMR1H en premier
TMR1H = 0x00;
TMR1L = 0x00;
// mise en route du timer1
T1CONbits.TMR1ON=1;
}
void initCaptureTimer1(void){
T3CONbits.T3CCP2=0;
}