jeudi 28 juin 2012

MCU et communication par port série

Dans ma rubrique précédente j'ai décris un convertisseur de tension pour établir une communication via un port COM entre le PC et un MCU installé en développement sur la platine sans soudure. Ajourd'hui j'ai écris un programme pour tester l'utilisation de ce convertisseur. J'ai monté sur la platine un MCU PIC10F202 et utiliser GP2 comme sortie pour contrôler une DEL et GP1 est la sortie de communication avec le port COM du PC tandis que GP3 reçoit du PC.

Code source MPASM

;programme pour tester le convertisseur de tension
;pour port série
; Une DEL est branchée sur GP2 et son intensité est contrôlée par PWM
; la DEL est contrôlée par l'ordinateur via un port série
; lorsque le MCU est réinitialisé il envoie le message "PRET" à l'ordinateur
; les touches suivantes servent à contrôler la DEL
; 'o' allume la DEL à pleine intensité
; 'f' éteint la DEL
; 'u' augmente l'intensitée de la DEL
; 'd' diminue l'intensité de la DEL

include
__config _WDTE_OFF & _MCLRE_OFF & _CP_OFF

;constantes

OPTION_INI EQU B'00000001' ; configuration du registre OPTION pour que le pré-scaler soit attribué au TIMER0 avec un diviseur 1:4
TRIS_INI EQU 0x9 ; confirguration TRIS -> GP0,GP3 entrées, GP1 et GP2 sorties

BAUD_RATE EQU .9600
BIT_DLY EQU .1000000/BAUD_RATE ; microsecondes par bit à 9600BAUD
F_UART_RX_ERR EQU 0 ; indicateur erreur réception UART
F_LED_FULL EQU 1 ; indicateur LED à intensité maximale
F_LED_OFF EQU 2 ; indidcateur LED éteint

;macros pré-processeur
#define UART_OUT GPIO, GP1
#define UART_INP GPIO, GP3
#define LED GPIO, GP2


;variables

cblock 8
flags : 1 ; indicateurs booléins
uart_bit_cnt : 1 ; compteur de bit pour réception et transmission UART
uart_byte : 1 ; octet à transmettre
msg_ptr : 1 ; pointeur de caractères pour l'envoie des chaines de caratères
led_pwm : 1 ; compteur de modulation à largeur d'impulsion pour contrôler l'intensité de la DEL
led_level : 1 ; niveau d'intensité de la DEL
endc

org 0
goto init

;;;;;;; uart_send ;;;;;;;;;;;;;
;; envoie d'un octet vers le port COM du PC
;;
uart_send
bcf UART_OUT ; start bit
movlw ~((BIT_DLY/4)-2)
movwf TMR0
movlw .9
movwf uart_bit_cnt
movfw TMR0 ; délais start bit
skpz
goto $-2
uart_send_bit_loop
movlw ~((BIT_DLY/4)-2)
movwf TMR0
decfsz uart_bit_cnt,F
goto send_next_bit
bsf UART_OUT ; stop bit
movfw TMR0
skpz
goto $-2 ; boucle délais stop bit
return
send_next_bit
rrf uart_byte,F
skpc
goto send_0
bsf UART_OUT
goto uart_send_wait_dly
send_0
bcf UART_OUT
uart_send_wait_dly
movfw TMR0
skpz
goto $-2
goto uart_send_bit_loop


;;;;;;;; uart_receive ;;;;;;;;;;;;;;
; réception d'un octet envoyé par le PC
; via un port COM.
uart_receive
movlw ~(BIT_DLY/4)
movwf TMR0
movfw TMR0 ; délais du start bit
skpz
goto $-2
bcf flags, F_UART_RX_ERR
movlw .8
movwf uart_bit_cnt
uart_rx_bit_loop
movf uart_bit_cnt
skpnz
goto uart_rx_stop_bit
movlw ~(BIT_DLY/4)
movwf TMR0
bsf STATUS, C
btfss UART_INP
bcf STATUS, C
rrf uart_byte, F
movfw TMR0 ; délais de bit
skpz
goto $-2
decf uart_bit_cnt,F
goto uart_rx_bit_loop
uart_rx_stop_bit
btfsc UART_INP
return
;framing error
bsf flags, F_UART_RX_ERR
return

message_pret
addwf PCL,F
dt "PRET",0xD,0

message_erreur
addwf PCL, F
dt "ERREUR",0xD,0

;;;;; prompt_ready ;;;;;;;;;;;
;;; envoie du message pret
prompt_ready
clrf msg_ptr
msg_loop
movfw msg_ptr
call message_pret
xorlw 0
skpnz
return
movwf uart_byte
call uart_send
incf msg_ptr,F
goto msg_loop

;;;;;; send_error_msg ;;;;;;;;;;;;;;;
;; signale une erreur lors de la réception
;; d'un octet par uart_receive
send_error_msg
clrf msg_ptr
msg_err_loop
call message_erreur
xorlw 0
skpnz
return
movwf uart_byte
call uart_send
incf msg_ptr,F
goto msg_err_loop

;;;;;;; led_control ;;;;;;;;;;;;;;;;;;;;;
;; contrôle l'intensitée de la DEL
led_control
incf led_pwm, F
movfw led_level
subwf led_pwm, W
skpnc
goto lc_led_off
bsf LED
return
lc_led_off
bcf LED
return

;;;;;; prodécude d'initialisation ;;;;;;;;;;;;;;;;
init
movwf OSCCAL
movlw OPTION_INI
option
movlw TRIS_INI
tris GPIO
clrf flags
bcf LED ; led EDIT
bsf flags, F_LED_OFF
bsf UART_OUT ; cette sortie doit-être au niveau 1 lorsqu'il n'y a pas de transmission
call prompt_ready
clrf led_pwm
clrf led_level

;;;;;;;;;;;; routine principale ;;;;;;;;;;;;;;;;;;;;
main
btfsc UART_INP
goto main02
call uart_receive
btfsc flags, F_UART_RX_ERR
goto send_nack
movlw 'u'
xorwf uart_byte,W
skpnz
goto increase_led_level
movlw 'd'
xorwf uart_byte,W
skpnz
goto decrease_led_level
movlw 'f'
xorwf uart_byte,W
skpnz
goto led_max_level
movlw 'o'
xorwf uart_byte,W
skpz
goto main02
led_off
bsf flags, F_LED_OFF
bcf flags, F_LED_FULL
bcf LED
clrf led_level
movlw 'O'
movwf uart_byte
call uart_send
goto main
led_max_level
bsf LED
movlw 0xFF
movwf led_level
bsf flags, F_LED_FULL
bcf flags, F_LED_OFF
movlw 'F'
movwf uart_byte
call uart_send
goto main02
increase_led_level
bcf flags, F_LED_OFF
movlw 0xFF
xorwf led_level,W
skpnz
goto led_max_level
incf led_level,F
swapf led_level,W
andlw 0xF
addwf led_level,F ; linéarisation du contrôle d'intensité
skpnc
goto led_max_level
goto main02
decrease_led_level
bcf flags, F_LED_FULL
movf led_level,F
skpnz
goto led_off
decf led_level, F
swapf led_level,W
andlw 0xF
skpz
subwf led_level,F ; linéarisation du contrôle d'intensité
main02
btfsc flags, F_LED_FULL
goto main
btfsc flags, F_LED_OFF
goto main
call led_control
goto main
send_nack
call send_error_msg
goto main

end

description du code

Au démarrage du MCU la procédure d'initialisation est exécutée et lorsqu'elle est complétée le message 'PRET' est envoyée à l'ordinateur. Ensuite le programme entre dans la boucle principale.

La boucle principale vérifie l'état de GP3 et si la valeur est à zéro c'est que le PC transmet un start bit au MCU. Dans ce cas la procédure uart_receive est appellée et ensuite le caractère reçu est interprété dans la procédure principale. Si le caratère reçu ne fait pas partie des commandes il est simplement ignoré. Lorsqu'il n'y a pas de réception la procédure principale appelle en boucle la routine led_control qui ajuste l'intensitée de la DEL par modulation PWM.

La modulation PWM fonctionne de la façon suivante. la variable led_pwm est un compteur qui est incrémenté à chaque appelle de la routine et la valeur de cette variable est comparée avec led_level. Lorsque led_pwm est plus grand ou égal à led_level la DEL est éteinte autrement elle est allumée. si on regarde le signal à la sortie GP2 avec un oscilloscope on voie un onde carré dont la durée de la portion HAUTE varie en fonction de l'intensité. A intensité faible cette portion est étroite et à intensité élevée elle est large. En fait la DEL clignote à une fréquence trop rapide pour que ce soit perceptible à l'oeil. On ne perçoit qu'une variation d'intensité.

Notez la présence des indicateurs booléens F_LED_FULL et F_LED_OFF. Lorsque l'un ou l'autre de ces indicateurs est actif il n'est pas nécessaire d'appeller la routine led_control.

Si vous étudiez les portions de code increase_led_level et decrease_led_level vous constaterez que l'incrémentation n'est pas une constante mais une fraction de la valeur de la variable led_level. Ceci a pour but de linéariser la fonction d'intensité. Si on utilisait une constante l'effet sur l'intensité serait beaucoup plus prononcé à faible intensité qu'à haute intensité. l'incrément est 1/16 de la valeur led_level avec un planché de 1. la division par 16 est obtenu seulement avec 2 instructions.

swapf led_level,W
andlw 0xF

La procédure uart_send est utilisée pour envoyer l'octet qui est dans la variable uart_byte vers la sortie GP2 qui est reliée à l'adapteur RS-232.
la variable uart_bit_cnt est utilisée pour compter les bits à envoyer. Le TMR0 du MCU sert à contrôler le délais de bit. à 9600BAUD la durée d'un bit est d'environ 104 micro-secondes. Cette valeur est critique et j'ai du l'ajustée en vérifiant le signal avec un oscilloscope1. Le pré-scaler du timer étant réglé à 1:4 je divise 104/4. Puisque le timer est incrémenté plutôt que décrémenté il faut inverser la valeur avec l'opérateur '~'. Mais avec ce compte la durée de bit était de 113usec et le PC ne reconnaissait pas les octets qu'ils recevait du MCU. J'ai donc soustrait 2 comptes ce qui correspond à 8usec. Avec cette valeur ça fonctionne très bien.

capture d'écran de la session terminal (windows 7)


La prodécure uart_receive utilise les mêmes variables que uart_send ainsi que le timer0 de la même façon. La communication est donc semi-duplex. C'est à dire que le MCU ne peut recevoir et envoyer en même temps.

suggestion de projet

Serait-il possible de modifier ce programme pour contrôler une servo-moteur à partir de l'ordinateur plutôt qu'une DEL. Habituellement les servo-moteurs fonctionnent avec une impulsion de largeur variable qui doit se répéter à toutes les 20 msec. La largeur de l'impulsion détermine la position du servo. Tel que je conçois le problème, il faut que la vérification de GP3 soit intégrée à l'intérieur du délais qui contrôle le servo-moteur pour ne pas manquer l'envoie des commandes par le PC. l'intervalle maximale entre chaque vérification de GP3 doit-être inférieur à 52 usec.

Cette modification n'est pas aussi simple qu'il peut sembler à prime abord. La réception ou la transmission d'un octet dure plus de 1 msec hors même pendant la réception ou la transmission d'un octet il faut respecter la durée et la fréquence des impulsions envoyées au servo si on veut qu'il conserve sa position. Est-ce possible avec un MCU comme le PIC10F202 qui a un cycle d'instruction de 1 usec et un seul TIMER?
Il s'agit d'un problème multi-tâche en temps réel.



NOTES:
1)Un oscilloscope est un instrument coûteux et cependant quasiment indispensable pour faire ce genre de travail. Seeed studio cependant vend des oscilloscopes numérique de poche très peut coûteux. Ainsi le DSO nano coûte 89US$. Pour des signaux de moins de 500Khz il peut-être très utile. Personnellement il m'a été indispensable dans le développement de mes projets PIC. Seeed Studio vend aussi le DSO quad pour 199US$. Ces 2 appareils sont aussi disponible chez Roboshop. Le quad a une fréquence d'échantillonage de 72Ms/sec. C'est très bien pour un appareil de ce prix.

Aucun commentaire:

Publier un commentaire

Remarque : Seuls les membres de ce blogue sont autorisés à publier des commentaires.