Logiciels > Programmation PIC > Bases > MP > Peripheral Pin Select (PPS)

Dernière mise à jour : 13/08/2017

Présentation

Dans ce programme d'exemple, nous allons voir comment configurer des broches d'entrée/sortie sur des broches "remappable" d'un PIC 18F47J13. L'objectif est "d'orienter" (de brancher) les lignes Tx et Rx du module UART2 et les lignes d'interruption externes INT1 et INT2 sur les broches suivantes du PIC :
- ligne Tx UART2 sur broche RD6
- ligne Rx UART2 sur broche RD7
- ligne INT1 sur broche RB1
- ligne INT2 sur broche RB2
A chaque interruption INT0, INT1 ou INT2 provenant des broches respectives RB0, RB1 ou RB2, le programme enverra un octet de valeur définie sur la ligne Tx (valeur $55 sur interruption INT0, valeur $AA sur interruption INT1 et valeur $F7 sur interruption INT2).
A chaque interruption UART2_Rx provenant de la broche RD7, le programme affichera en binaire la valeur du dernier octet reçu, sur 8 LED.

Avant de continuer...

Certaines notions ne sont que survolées. Pour de plus amples informations, merci de vous reporter aux pages suivantes :
Configuration minimale d'un PIC - Configuration des entrées sorties
Configuration oscillateur - Oscillateur interne ou externe, quartz, réseau RC, ...
Interruptions - Detection d'un évenement externe ou interne

Sélection des broches périphériques (PPS = Peripheral Pin Select)

Pour les "petits" PIC en boîtier DIP/DIL traversants, les broches d'entrée/sortie sont figées par construction : tel ou tel module interne (I2C/SPI, UART par exemple) est raccordé "en dur" à telle ou telle broche. Par exemple sur le PIC16F628A, on est obligé d'utiliser les broches RB1 et RB2 si on veut utiliser l'unique module UART (RB1 pour Rx-réception des données et RB2 pour Tx-transmission des données). Sur des PIC plus évolués (PIC18, PIC24 ou PIC32), les entrées/sorties de certains modules internes du PIC peuvent être "branchées" sur des broches physiques "remappable" (assignables). On reconnait facilement les broches assignables (remappables) grâce à leur libellé qui contient un terme de type RP2 ou RP14 (RP = Remappable Pin). Avec le PIC 18F47J13 par exemple, nous disposons de deux modules UART appelés UART1 et UART2. Les broches TX et RX du module UART1 sont câblées en dur, respectivement sur les broches RC6/RP17/TX1 et RC7/RP18/RX1. Le module UART2 quant à lui n'est pas "pré-câblé", et pour pouvoir l'utiliser il faut spécifier (par programmation) à quelles broches physiques du PIC on souhaite le "raccorder". Dans notre exemple, nous raccorderons la ligne TX2 (TX de UART2) sur la broche RD6, et la ligne RX2 (RX de UART2) sur la broche RD7. C'est un choix tout à fait arbitraire, on aurait pu choisir d'autres broches physiques réassignables comme RD0 et RD1. Pour ce "remappage", nous utiliserons des commandes incluses dans la librairie native de MikroPascal. Mais rien n'interdit, bien sûr, d'écrire tout le code nécessaire directement au niveau des registres. Personnellement, je pense qu'il serait dommage de passer à côté de cette belle solution de simplicité ;-)

PIC 18F47J13 et PPS

Passons maintenant au côté pratique, avec notre fameux PIC 18F47J13 que j'ai choisi en version QFP 44 broches (11 broches sur chacun des 4 côtés du boîtier CMS).

pic_tuto_pps_001a

Dans le schéma qui précède, nous voyons la 6ème LED, câblée sur RC5, qui est allumée, toutes les autres sont éteintes. Cela est du au fait que le PIC vient de recevoir sur sa broche RX, le caractère ESPACE venant d'un émulateur de terminal. Ce caractère, en code ASCII, a pour valeur 32 en décimal, soit $20 en hexadécimal ou %00100000 (seul le bit #5 est actif). Comme la valeur du dernier octet reçu sur la ligne Rx de l'UART2 est copié directement sur le port C du PIC (nous verrons cela dans le code ci-après), cela allume la LED correspondant au bit #5. A gauche du PIC, nous voyons que le bouton SW2, qui déclenche l'interruption INT1, est enfoncé. Cette action a provoquée l'envoi du texte "INT1" sur la broche TX de l'UART2 (et donc sur RD6), texte mis en relief sur l'écran de l'émulateur de terminal.

pic_tuto_pps_001b.

Voyons donc maintenant le code complet correspondant à tout cela.

program electronique_tuto_pic_mp_pps_18f47j13;

var
iRxData: byte;
bIntINT0, bIntINT1, bIntINT2, bIntUART2Rx: boolean;

procedure CPU_Init;
begin

// PPS Mapping
Unlock_IOLOCK(); // déverrouillage pour autorisation remappage des broches physiques
PPS_Mapping_NoLock(4, _INPUT, _INT1); // maps INT1 Input to RP4/RB1 (QFP_9)
PPS_Mapping_NoLock(5, _INPUT, _INT2); // maps INT2 Input to RP5/RB2 (QFP_10)
PPS_Mapping_NoLock(23, _OUTPUT, _TX2_CK2); // maps TX2 output to RP23/RD6 (QFP_4)
PPS_Mapping_NoLock(24, _INPUT, _RX2_DT2); // maps RX2 Input to RP24/RD7 (QFP_5)
Lock_IOLOCK(); // verrouillage après remappage des broches physiques

// désactivation du module convertisseur A/N, broches de type "logique"
ADCON0 := 0; // turn off analog inputs
ANCON0 := %11111111;
ANCON1 := %00011111;

// désactivation comparateurs
CM1CON := $00;
CM2CON := $00;
CM3CON := $00;

TRISA := $FF; // toutes lignes port A en entrée
TRISB := $FF; // toutes lignes port B en entrée
TRISC := $00; // toutes lignes port C en sortie
TRISD := %10000000; // ligne RD7 seule en entrée (UART2-Rx)

// interrupts
INTCON := $00;
INTCON.INT0IF := 0;
INTCON.INT0IE := 1; // activation interruptions INT0
INTCON3 := 0;
INTCON3.INT1IF := 0;
INTCON3.INT1IE := 1; // activation interruptions INT1
INTCON3.INT2IF := 0;
INTCON3.INT2IE := 1; // activation interruptions INT2
INTCON2 := $00;
INTCON2.NOT_RBPU := 0; // pullup port B laissé activé
INTCON2.INTEDG0 := 0; // interuptions sur front descendant pour INT0
INTCON2.INTEDG1 := 0; // interuptions sur front descendant pour INT1
INTCON2.INTEDG2 := 0; // interuptions sur front descendant pour INT2

// init UART2
TXSTA2 := $00;
//TXSTA2.TX9 := 0; // Tx-transmission en mode 8-bits
TXSTA2.TXEN := 1; // UART2 Tx activé
//TXSTA2.SYNC := 0; // mode Asynchrone
RCSTA2 := $00;
RCSTA2.CREN := 1; // UART2 Rx activé en mode "Continu"
//RCSTA2.SPEN := 1; // Interruption UART2 Rx activée plus tard
//RCSTA2.TX9 := 0; // transmission en mode 8-bits
UART_Remappable_Init(9600); // vitesse (baudrate) = 9600 bauds
Delay_ms(100); // délai pour stabilisation UART

RC2IF_bit := 0; // PIR3.RC2IF - reset drapeau interruption UART2 Rx
RC2IE_bit := 1; // PIE3.RC2IE - activation interruption UART2 Rx
RCSTA2.SPEN := 1; // activation interruption UART2 Rx

RCON.IPEN := 0; // interruptions sans degré de priorité
INTCON.PEIE := 1; // activation des interruptions "périphériques"
INTCON.GIE := 1; // activation générale des interruptions

// initialisation des variables du programme
bIntINT0 := false;
bIntINT1 := false;
bIntINT2 := false;
bIntUART2Rx := false;

end;

procedure Interrupt_INT0;
begin
INT0IF_bit := 0; // INT0IF_bit = INTCON.INT0IF
bIntINT0 := true; // indication d'une interruption qui sera utilisée
// dans le corps principal du programme (main)
end;

procedure Interrupt_INT1;
begin
INT1IF_bit := 0; // INT1IF_bit = INTCON3.INT1IF
bIntINT1 := true; // indication d'une interruption qui sera utilisée
// dans le corps principal du programme (main)
end;

procedure Interrupt_INT2;
begin
INT2IF_bit := 0; // INT2IF_bit = INTCON3.INT2IF
bIntINT2 := true; // indication d'une interruption qui sera utilisée
// dans le corps principal du programme (main)
end;

procedure Interrupt_UART2;
begin
RC2IF_bit := 0; // RC2IF_bit = PIR3.RC2IF
iRxData := RCREG2; // récupération octet reçu sur UART2 Rx
bIntUART2Rx := true; // indication d'une interruption qui sera utilisée
// dans le corps principal du programme (main)
end;

procedure Interrupt;
begin

// INT0 Interrupt
if INT0IF_bit then
Interrupt_INT0;

// INT1 Interrupt
if INT1IF_bit then
Interrupt_INT1;

// INT2 Interrupt
if INT2IF_bit then
Interrupt_INT2;

// UART2 Int
if RC2IF_bit then
Interrupt_UART2;

end;

begin
CPU_Init;
while true do
begin

// interruption sur INT0 ?
if bIntINT0 then
begin
bIntINT0 := false;
UART_Remappable_Write_Text('INT0' + #13); // envoi sur UART2 Tx
LATC := $01; // activation LED #1 seule
end;

// interruption sur INT1 ?
if bIntINT1 then
begin
bIntINT1 := false;
UART_Remappable_Write_Text('INT1' + #13); // envoi sur UART2 Tx
LATC := $02; // activation LED #2 seule
end;

// interruption sur INT2 ?
if bIntINT2 then
begin
bIntINT2 := false;
UART_Remappable_Write_Text('INT2' + #13); // envoi sur UART2 Tx
LATC := $04; // activation LED #3 seule
end;

// interruption sur UART2 Rx ?
if bIntUART2Rx then
begin
bIntUART2Rx := false;
UART_Remappable_Write(iRxData); // envoi sur UART2 Tx
LATC := iRxData;
end;

end;
end.


Comme vous pouvez le constater, j'ai utilisé des "sous-routines" pour chacune des quatre interruptions possibles. Cela n'a rien d'obligatoire, toutes les routines liées aux interruptions auraient pu tenir dans la même procédure appelée "Interrupt". Cette façon de faire permet simplement d'organiser le code et d'y voir plus clair lors des phases de développement et de maintenance. Mais nous n'allons pas nous attarder là-dessus, les lignes qui nous intéressent plus particulièrement sont les suivantes :

procedure CPU_Init;
begin

// PPS Mapping
Unlock_IOLOCK(); // déverrouillage pour autorisation remappage des broches physiques
PPS_Mapping_NoLock(4, _INPUT, _INT1); // maps INT1 Input to RP4/RB1 (QFP_9)
PPS_Mapping_NoLock(5, _INPUT, _INT2); // maps INT2 Input to RP5/RB2 (QFP_10)
PPS_Mapping_NoLock(23, _OUTPUT, _TX2_CK2); // maps TX2 output to RP23/RD6 (QFP_4)
PPS_Mapping_NoLock(24, _INPUT, _RX2_DT2); // maps RX2 Input to RP24/RD7 (QFP_5)
Lock_IOLOCK(); // verrouillage après remappage des broches physiques

// etc

end;


Ce sont précisément ces lignes de commande qui permettent de spécifier au PIC la façon dont on veut "brancher/router" (remapper) les modules internes vers les broches physiques. La première ligne de ce bloc, Unlock_IOLOCK donne la main aux opérations de réassignation des broches physiques (remappage). Chacune des quatre lignes suivantes PPS_Mapping_NoLock permet l'assignation d'une broche physique particulière à une entrée ou à une sortie d'un module interne. La dernière ligne Lock_IOLOCK (re)verrouille le système, empêchant toute réassignation ultérieure des broches physiques. Intéressons-nous donc de plus près aux lignes PPS_Mapping_NoLock.
La commande PPS_Mapping_NoLock, qui fait partie de la librairie native de MikroPascal, nécessite trois paramètres :

function PPS_Mapping_NoLock(rp_num, input_output, funct_name : byte) : byte;

Les paramètres de cette fonction doivent être utilisés (renseignés) ainsi :

Parlons donc un peu plus du paramètre func_name, puisque c'est lui qui réclame le plus de recherches. Le datasheet (manuel technique) du microcontrôleur comporte un paragraphe appelé "Peripheral Pin Select", qui pourrait bien nous être utile. On y trouve en effet deux tableaux, un spécifique aux entrées (input sources) et l'autre spécifique aux sorties (output sources). C'est dans ces deux tableaux que nous allons localiser les valeurs des paramètres qui correspondent aux modules que nous voulons utiliser. Dans le tableau des entrées, nous devons rechercher les lignes relatives aux interruptions externes INT1 et INT2, ainsi que celle relative à l'entrée du module UART2 (RX2). 

pic_tuto_pps_18f47j13_pps_inputs

Et dans le tableau des sorties, nous devons localiser la ligne relative à la sortie du module UART2 (TX2).

pic_tuto_pps_18f47j13_pps_outputs

Finalement la tâche n'est pas si ardue que ça.

Sauf que dans le code que j'ai proposé, on peut voir les valeurs de paramètres suivantes :
_INT1, _INT2, _TX2_CK2 et _RX2_DT2

et non pas les valeurs de paramètres suivantes :
INT1, INT2, TX2/CK2 et RX2/DT2

Les valeurs autorisées sont en effet indiquées dans l'aide en ligne de MikroPascal, à la rubrique... Peripheral Pin Select (PPS) :

pic_tuto_pps_mp_help_pps_inputs

pic_tuto_pps_mp_help_pps_outputs

Avec un peu d'habitude, on s'y retrouve assez vite ;-)