<< The Fribotte Homepage >>
Un club de passionnés en robotique participant à la coupe de France E=M6.
[Accueil] [Qui sommes-nous ?] [Robots] [Coupe e=m6] [BD Technique] [Forum] [Reportages] [Liens] [WiKiFri]

Fribotte

 

La Programmation des pics
par Bigonoff
Premiere partie - pic 16f84 - Révision 4

Téléchargement des fichiers originels ici
Publié le 26/10/2001

19. La norme ISO 7816 (suite)

Index coursIndex du cours
Chapitre précédent19. La norme ISO 7816
Chapitre suivantAnnexe1 : Questions fréquemment posées (F.A.Q.)

 

19. La norme ISO 7816 (suite)
19.7 Réception d’un octet

Maintenant que vous avez compris comment recevoir un octet en mode série asynchrone, il est temps d’écrire notre sous-programme de réception d’un octet. Dans cet exercice, nous ne vérifierons pas si le bit de parité reçu est correct. Je vous laisse de soin d’intégrer ce test vous-même si cela vous intéresse. Dans ce cas, vous devrez lire 9 bits et vérifier si le nombre de " 1 " reçus est pair. Il y a plusieurs méthodes possibles.

Notre sous-programme devra donc effectuer les opérations suivantes :

  • Attendre le début du start-bit
  • Attendre une durée de 93 + 46 instructions
  • Pour chacun des 8 bits, lire le bit et le placer dans le caractère reçu en bonne position
  • Se positionner quelque part dans les stop-bits

Voici donc notre sous-programme :

*********************************************************************
; Réception d'un octet provenant du maître
;*********************************************************************
;---------------------------------------------------------------------
; Caractère lu dans W. La parité pas n'est pas vérifiée
;---------------------------------------------------------------------
Receive      
    ; attendre début start-bit
; ------------------------
  btfsc SERIAL ; Tester si start bit arrivé
  goto Receive ; non, attendre
    ; se positionner sur le milieu du 1er bit utile
; ---------------------------------------------
  call temp_1bd ; attendre 1bit et demi
    ; réception du caractère
; ----------------------
  movlw 0x8 ; pour 8 bits
  movwf cmptbts ; dans compteur de bits
Recloop      
  bcf STATUS , C ; Carry = 0
  btfsc SERIAL ; tester si bit = 0
  bsf STATUS , C ; Carry = bit reçu
  rrf caract , f ; faire entrer le bit par la gauche
  call temp_1b ; attendre milieu caractère suivant
  decfsz cmptbts , f ; décrémenter compteur de bits
  goto Recloop ; pas dernier, suivant
    ; on pointe actuellement sur le centre du bit de parité
; reste donc à attendre +- 1.5 bits pour être dans le second stop-bit
; ------------------------------------------------------------------------------
  call temp_1bd ; Attendre 1,5 bit
  movf caract , w ; charger caractère lu
  return   ; et retour

Ce sous-programme ne contient pas de difficultés particulières. Remarquez que plutôt que de positionner chaque bit reçu directement à la bonne position, on le fait entrer dans b7 en se servant d’une instruction " rrf ". Le bit précédent est de fait reculé en b6 et ainsi de suite. Le premier bit lu se retrouve donc en b0, le dernier restant en b7.

Concernant la dernière temporisation, nous pointons à ce moment sur le centre du 9ème bit, c’est à dire le bit de parité. Comme nous ne le traitons pas, inutile de le lire. Nous devons alors nous positionner sur un endroit où la ligne est repassée au niveau " 1 ", c’est à dire un des stop-bits, afin de pouvoir éventuellement commencer une attente d’un nouveau caractère.

Nous devons donc nous attendre entre 0.5 et 2.5 bits. La sous-routine 1.5 bit est en plein dans cette zone et est directement utilisable.

Le présent sous-programme utilise des variables que nous déclarerons plus loin.

19.8 L’émission d’un caractère

N’oublions pas que notre carte n’émet qu’en réponse à une interrogation du " maître ". Donc, notre sous-programme d’émission sera appelé après le sous-programme de réception d’un octet.

Il est important également de se souvenir que nous travaillons en mode half-duplex, c’est à dire que la même ligne sert pour les entrées et les sorties. Comme chacun des interlocuteurs parle à tour de rôle, il faut laisser à chacun le temps de repasser en lecture après l’envoi de son dernier message. Ceci s’appelle " temps de retournement ". Il faut également se rappeler que notre routine de réception s’achèvera quelque part au milieu des stop-bits, il faudra également laisser à l’émetteur le temps de finir d’envoyer la fin de ses stop-bits.

Un bon compromis et la facilité d’écriture du programme nous permet de choisir une attente de 1.5 bit avant de commencer à émettre. J’ai choisi cette valeur car cette temporisation permet d’initialiser le timer. Cette valeur n’est cependant pas critique. Il faut simplement répondre après que le " maître " soit placé en mode de réception, et avant qu’il ne considère que vote carte n’a pas répondu.

Notre programme va donc effectuer les opérations suivantes :

  • Attendre temps choisi avant émission
  • Passer en émission et envoyer le start-bit
  • Envoyer les 8 bits en commençant par b0
  • Pour chaque bit " 1 " envoyé, inverser la parité, pour obtenir une parité paire
  • Envoyer la parité
  • Envoyer les 2 stop-bits
  • Repasser en réception

Voici donc notre sous-programme :

;*********************************************************************
; Envoi d'un octet vers le lecteur de carte
;*********************************************************************
;------------------------------------------------------------------------------
; envoie l'octet contenu dans le registre w vers le lecteur de carte
;------------------------------------------------------------------------------
Send      
  movwf caract ; Sauver caractère à envoyer
  call temp_1bd ; attendre 1 bit et demi
  BANK1   ; passer banque1
  bcf SERIAL ; port série en sortie
  BANK0   ; repasser banque0
    ; envoyer start-bit
; --------------------
  bcf SERIAL ; envoyer 0 : start-bit
  clrf parite ; effacer bit de parité
  movlw 8 ; pour 8 bits à envoyer
  movwf cmptbts ; dans compteur de bits
  call temp_1b ; attente entre 2 bits
    ; envoyer 8 bits de data
; ---------------------------
Send_loop      
  rrf caract , f ; décaler caractère, b0 dans carry
  rrf caract , w ; carry dans b7 de w
  andlw 0x80 ; garder bit à envoyer en position b7
  xorwf parite , f ; positionner parité
  xorwf PORTB , w ; Garder 1 si changement sur SERIAL
  xorwf PORTB , f ; si oui, inverser RB7
  call temp_1b ; attente entre 2 bits
  decfsz cmptbts , f ; décrémenter compteur de bits
  goto Send_loop ; pas dernier, suivant
    ; envoyer parité
; ------------------
  movf parite , w ; charger parité paire calculée
  xorwf PORTB , w ; Si serial différent de bit à envoyer
  xorwf PORTB , f ; alors inverser RB7
  call temp_1b ; attendre fin de parité
    ; envoyer 2 stop-bits
; -------------------
  BANK1   ; passer banque1
  bsf SERIAL ; repasser en entrée (et niveau haut)
  BANK0   ; passer banque0
  call temp_1b ; attendre temps entre 2 bits
  call temp_1b ; attendre temps entre 2 bits
  return   ; et retour

Si vous êtes attentifs, vous avez remarquer une légère inversion en ce qui concerne la fin du protocole. En effet, plutôt que d’envoyer un niveau 1 (stop-bit) puis d’attendre 2 bits et enfin de repasser en entrée, nous sommes passé en entrée puis avons attendu 2bits.

Ceci est strictement identique, car la résistance de rappel au +5V du PORTB, que nous avons activée se charge d’imposer un niveau haut sur RB7 dès que cette pin est remise en entrée.

La routine servant à envoyer les 8 bits utilise l’instruction " xorwf " au lieu de tester si le bit à émettre vaut 1 ou 0. La routine commence par placer le bit à émettre en position b7. Pourquoi b7 ? Et bien tout simplement parce que c’est également b7 dans le PORTB que nous devrons modifier. Nous utilisons en effet RB7. La procédure utilisée permet de positionner en même temps le bit de parité. Effectuez l’opération manuellement sur papier pour vous en convaincre. Tentez d’écrire une routine utilisant " btfss " et " btfsc " et comparez les résultats obtenus.

Un petit mot concernant l’envoi du bit proprement dit, c’est à dire les 2 instructions :

  xorwf PORTB , w ; Garder 1 si changement sur SERIAL
  xorwf PORTB , f ; si oui, inverser RB7

Nous commençons ici par lire le PORTB et nous effectuons un " xorlw " avec le bit à envoyer contenu dans " W ". Comme "W" ne contient que ce bit, RB0 à RB6 ne seront pas modifiés par les opérations suivantes.

Si le bit7 contenu dans "W" est différent de celui présent sur RB7, nous obtenons b7 = 1 dans W. Dans le cas où b7 de W est identique à RB7, nous obtenons 0. N’oublions pas en effet que le ",W" permet de placer le résultat dans "W".

Si nous appliquons " W " sur le PORTB en effectuant un " xorwf ", nous inverserons RB7 uniquement si b7 de " W " vaut 1, c’est à dire, en d’autres mots : Nous inverserons RB7 uniquement si son niveau actuel est différent du niveau que nous devons envoyer.

Ceci paraît un peu " tordu ", mais si vous essayez d’écrire cette routine autrement, vous verrez qu’en effectuant plusieurs essais, vous arriverez à un résultat identique, tout ceci à cause de la parité à gérer.

Remarquez que vous pouvez ignorer la vérification du bit de parité en réception, c’est votre problème. Par contre, vous êtes obligé de positionner correctement celle-ci à l’émission, car il y a de fortes chances pour que le maître vérifie cette parité.

19.9 Initialisation

Nous allons maintenant étudier le corps de notre programme principal. Comme tout programme qui se respecte, nous commencerons par l’initialisation. Celle-ci va être très simple, elle se limite à initialiser le registre OPTION.

;*********************************************************************
; INITIALISATIONS
;*********************************************************************
  org 0x000 ; Adresse de départ après reset
init      
  BANK1   ; passer banque1
  movlw OPTIONVAL ; charger masque
  movwf OPTION_REG ; initialiser registre option
  BANK0   ; passer banque0

 

19.10 Envoi de l’ATR

ATR signifie Answer To Reset, c’est à dire réponse à un reset. C’est une commande envoyée par la carte lors d’une mise sous tension ou lors d’un reset généré par le maître via la broche " MCLR ".

Tout d’abord, nous allons attendre un peu que le maître soit prêt à recevoir notre ATR. Il peut être nécessaire d’ajuster ce temps en fonction des caractéristiques du maître.

;*********************************************************************
; PROGRAMME PRINCIPAL
;*********************************************************************
start      
    ; on commence par attendre un peu
; -------------------------------
  call temp_1bd ; attendre 1 bit et demi

Ensuite, nous pouvons envoyer l’ATR. Pour des raisons de facilité, nous avons écrit notre ATR dans la zone eeprom interne. J’ai choisi un ATR de 5 caractères que j’ai inventé. Consultez les caractéristiques du maître pour connaître les ATR valides de votre application.

;===============================================================
; ENVOI DE L'ATR
;===============================================================
;---------------------------------------------------------------------------
; Envoi d'un ATR fictif : l'ATR est dans les 5 octets de 0x04 à
; 0x00 de l'eeprom interne. L'ATR est écris en sens inverse
;---------------------------------------------------------------------------
  movlw 0x5 ; pour 5 octets
  movwf cmpt1 ; dans compteur de boucles = adresse
ATR_loop      
  decf cmpt1 , w ; adresse à lire = compteur de boucles-1
  call Rd_eeprom ; Lire un octet eeprom interne
  call Send ; Envoyer sur le décodeur
  decfsz cmpt1 , f ; décrémenter compteur
  goto ATR_loop ; pas fini, suivant

Remarques

L’utilisation de la commande " decfsz " facilite l’écriture des boucles, mais, comme le compteur de boucles est en même temps l’offset de la position en eeprom, l’ATR sera écrit à l’envers dans l’eeprom, c’est à dire du dernier vers le premier octet.

A l’endroit de l’appel de la sous-routine " Rd_eeprom ", le compteur de boucles variera de 5 à 1. Or, notre adresse eeprom variera de 4 à 0. Donc, l’opération " decf " permet de charger dans " W " la valeur du compteur de boucles – 1.

Le sous-routine " Rd_eeprom " n’est rien d’autre que notre macro de lecture de mémoire eeprom transformée en sous-programme. La voici :

;*********************************************************************
; Lecture d'un octet en eeprom interne *
;*********************************************************************
;-------------------------------------------------------------------------------------------------------
; Lecture d'un octet de l'eeprom interne. L'adresse est passée dans w.octet lu est dans W
;--------------------------------------------------------------------------------------------------------
Rd_eeprom      
  movwf EEADR ; adresse à lire dans registre EEADR
  bsf STATUS , RP0 ; passer en banque1
  bsf EECON1 , RD ; lancer la lecture EEPROM
  bcf STATUS , RP0 ; repasser en banque0
  movf EEDATA , w ; charger valeur lue dans W
  return   ; retour

Pensons également à inscrire notre ATR dans la zone eeprom :

;*********************************************************************
; DECLARATIONS DE LA ZONE EEPROM *
;*********************************************************************
  org 0x2100 ; adresse début zone eeprom
ATR DE 0x07 ; Réponse à l'ATR
  DE 0xAB ; B7 01 BB AB 07
  DE 0xBB  
  DE 0x01  
  DE 0xB7  

 

19.11 L’envoi du status

La norme ISO 7816 demande que chaque émission d’une réponse de la carte soit suivi de 2 octets de status qui indiquent la manière dont a été interprétée la commande.

J’ai inventé des status pour cet exercice. Le status " 80 00 " indiquera que la commande a été exécutée correctement. J’utiliserai également le status " 60 40 " pour indiquer que la commande n’existe pas.

Nous allons donc créer 2 sous-programme. Un qui envoie le status standard, l’autre qui envoie n’importe quel status.

;===============================================================
; ENVOI DU STATUS STANDARD
;===============================================================
;-----------------------------------------------------------------------
; Envoie le status standard, dans ce cas on a pris 0x80 0X00
;------------------------------------------------------------------------
Statstd      
  movlw 0x80 ; prendre 1er octet status
  call Send ; l'envoyer
  clrw   ; effacer w
  call Send ; envoyer 00
  goto classe ; et traiter classe
;================================================================
; ENVOI D'UN STATUS SPECIFIQUE
;================================================================
;----------------------------------------------------------------------------------------
; Envoie d'abord l'octet contenu dans w, puis l'octet contenu dans status2
;----------------------------------------------------------------------------------------
Statxx      
  call Send ; on envoie valeur
  movf status2 , w ; charger byte à envoyer
  call Send ; on envoie 2ème octet du status

 

19.12 Réception de la classe

Maintenant, notre carte passe en mode réception et attend sa première commande. Notre programme, pour des raisons de facilité ne gère qu’une seule classe. Nous nous contentons donc de lire l’octet, sans le vérifier ni le traiter. Il s’agit en effet d’un exercice didactique.

;=================================================================
; LECTURE DE LA CLASSE
;=================================================================
;-------------------------------------------------------------------------------------------------------------
; on considère dans cet exemple qu'il n'y a qu'une seule classe valide.
; on attend l'arrivée de la classe et on ne la traite pas
;-------------------------------------------------------------------------------------------------------------
classe      
  call Receive ; Lire le byte venant du maître

 

19.13 Réception de INS, P1, P2, et LEN

 

;===============================================================
; LECTURE DE INS, P1, P2, LEN
;===============================================================
;-----------------------------------------------------------------------------------------------------------
; INS sera placé dans la variable Ser_ins P1 sera placé dans Ser_P1 et P2 dans Ser_P2
; La longueur du champs de data sera dans Ser_len
;-----------------------------------------------------------------------------------------------------------
  movlw Ser_Ins ; pointer sur emplacement instruction
  movwf FSR ; initialiser pointeur indirection
read_loop      
  call Receive ; Lire un octet
  movwf INDF ; sauver dans emplacement prévu
  incf FSR , f ; pointer sur suivant
  btfss FSR , 0x4 ; Tester si adresse 0x10 atteinte
  goto read_loop ; non, octet suivant

Vous pouvez constater l’utilisation de l’adressage indirect pour sauvegarder les octets reçus dans 4 emplacements consécutifs. Nous choisirons les adresses 0x0C à 0x0F, ce qui nous permet facilement de détecter la fin de la commande. En effet, une fois le 4ème octet sauvé, FSR pointe sur 0x10, il suffit donc de tester son bit 4 pour tester la fin de la boucle, sans avoir besoin d’un compteur de boucles supplémentaire.

19.14 Contrôle de l’instruction reçue

Une fois la commande reçue, nous devons traiter les différentes commandes reçues. Dans notre exemple didactique, j’ai implémenté une seule instruction. La seule instruction valide est l’instruction 0x25.

Cette instruction calcule la somme de P1 et P2, et renvoie le résultat. Comme LEN contient la longueur de la chaîne de réponse, si LEN est supérieur à 1, la réponse sera complétée par des 0xFF. Toute autre instruction sera considérée comme incorrecte.

Voici notre test :

;==============================================================
; SWITCH SUIVANT INSTRUCTION RECUE
;==============================================================
;---------------------------------------------------------------------------------------------------------
; Nous allons imaginer que nous allons réagir à une instruction 0x25
; Toute autre instruction sera considérée comme incorrecte
;---------------------------------------------------------------------------------------------------------
  ; tester instruction reçue
; ------------------------
  movf Ser_Ins , w ; charger instruction reçue
  sublw 0x25 ; comparer avec 0x25
  btfsc STATUS , Z ; tester si identique
  goto Ins25 ; oui, traiter instruction 25
  ; traiter instruction incorrecte
; ------------------------------
  movlw 0x40 ; charger octet2 status à envoyer
  movwf status2 ; placer dans variable
  movlw 0x60 ; charger octet 1 status
  goto Statxx ; envoyer status

Nous voyons ici que si l’instruction reçue est 0x25, nous sautons au traitement de l’instruction. Dans le cas contraire, nous envoyons le status " 60 40 " qui signifie dans notre cas " instruction incorrecte ".

19.15 Traitement d’une instruction

Nous en arrivons maintenant au traitement de notre instruction proprement dite.

On va traiter cette instruction de la manière suivante :

  • Comme dans toute instruction, on renvoie l’instruction reçue
  • La carte renvoie la somme de P1 et de P2.
  • La trame d'envoi est complété par des 0xFF pour atteindre une longueur totale de data identique à Ser_Len
  • Ensuite le status standard est envoyé " 80 00 "
;================================================================
; TRAITER INSTRUCTION 25
;================================================================
Ins25      
    ; envoyer écho de la commande
; -------------------------------------
  movf Ser_Ins , w ; charger commande reçue
  call Send ; renvoyer en écho
    ; renvoyer P1 + P2
; ---------------------
  movf Ser_P1 , w ; charger P1
  addwf Ser_P2 , w ; + P2
  call Send ; envoyer résultat
    ; Tester longueur de la réponse
; -----------------------------------
  decf Ser_Len , f ; car déjà résultat envoyé
  btfsc STATUS , Z ; tester si complet
  goto Statstd ; oui, envoyer status standard
    ; compléter avec des 0xFF
    ; -------------------------------
Insloop      
  movlw 0xFF ; valeur à envoyer
  call Send ; envoyer 0xFF
  decfsz Ser_Len , f ; décrémenter compteur de boucles
  goto Insloop ; pas fini, suivant
    ; envoyer status standard
; -----------------------------
  goto Statstd ; envoyer status standard

 

19.16 Les variables

Il nous reste maintenant à déclarer les variables utilisées. Nous avons décidé ici d’utiliser des variables locales lorsque c’était possible :

;*********************************************************************
; DECLARATIONS DE VARIABLES
;*********************************************************************
  CBLOCK 0x00C ; début de la zone variables
    Ser_Ins : 1 ; instruction ISO7816
    Ser_P1 : 1 ; paramètre 1 ISO7816
    Ser_P2 : 1 ; paramètre 2 ISO7816
    Ser_Len : 1 ; longueur data ISO7816
    local1 : 1 ; variable locale 1
    local2 : 1 ; variable locale 2
    local3 : 1 ; variable locale 3
    local4 : 1 ; variable locale 4
    temp_sauvw : 1 ; sauvegarde de W pour temp
  ENDC   ; Fin de la zone
    ; routine ATR
; -----------
#DEFINE cmpt1 local1 ; compteur d'octets pour ATR
    ; sous-routine send et receive
; ----------------------------
#DEFINE caract local2 ; caractère à envoyer
#DEFINE parite local3 ; bit de parité
#DEFINE cmptbts local4 ; compteur de bits
    ; pour STATUS
; -----------
#DEFINE status2 local1 ; octet 2 du status
    ; pour instruction 25
; -------------------
#DEFINE cmpt2 local1 ; compteur d'octets

 

19.17 Conclusion

Vous savez maintenant communiquer en liaison série asynchrone. De plus vous avez les notions de base nécessaire pour réaliser une carte répondant à la norme ISO7816. Nul doute que vous trouviez de nombreuses applications mettant en œuvre la théorie vue ici.
 

 

Index cours
Index du cours
Chapitre précédent
19. La norme ISO 7816
Chapitre suivant
Annexe1 : Questions fréquemment posées (F.A.Q.)

 


Complétez cette page, posez vos questions et remarques ici : WiKiFri

Page http://fribotte.free.fr/bdtech/cours/pic16f84/PART1_cours19b.html modifiée le 14/10/2002.
Copyright fribotte@free.fr, libre de droit pour toute utilisation non commerciale.
Reproduction autorisée par simple mail