vendredi 26 juillet 2013

msp430 launchpad, partie 9

interface avec clavier PS/2

Interfacer un clavier PS/2 avec un MCU est plus simple que d'interfacer un clavier USB et ça demande moins de ressources. Ces dernier jours j'ai donc expérimenter avec un mini-clavier connecté au launchpad. Le programme démo contient 3 parties qui s'exécutent l'une à la suite de l'autre.

  1. allumées les 3 LEDs du clavier en séquence.
  2. Contrôler les LEDs avec les touches dédiées.
  3. Clavier musical, mono tonal.
Les fichiers du projet CCS sont sur https://github.com/Picatout/ps2-kbd-on-msp430g2553

Clavier PS/2

Les claviers PS/2 utilisent une interface qui ressemble au I2C. Il y a 2 lignes, 1 clock et 1 data et les 2 lignes peuvent-être contrôlées soit par le clavier soit par le MCU. La communication est dans les 2 sens, le MCU pouvant envoyer des commandes au clavier. C'est le MCU qui contrôle les LEDs qui sont sur le clavier et non le clavier lui-même. J'ai mis à la fin de cette chronique plusieurs liens donnant l'information que j'ai utilisé pour la création de l'interface PS/2 - MSP430.

C'est le clavier qui fourni le signal clock (environ 12Khz) même lorsque c'est le MCU qui envoie une commande au clavier. Lorsque le MCU veut envoyer une commande il maintien la ligne clock à 0 volt pendant au moins 100µsec et ensuite prends le contrôle de la ligne data et la mais aussi à zéro volt. Il libère ensuite la ligne clock pour que le clavier en reprenne le contrôle. Le MCU change la valeur de la ligne data lorsque la ligne clock est à zéro et attend le prochain cycle pour le bit suivant. Le bit le moins significatif est envoyé en premier. Après les 8 bits un bit de parité impair est envoyé. Ensuite c'est le stop bit. I y a donc 11 bits par octet en incluant le start bit. A chaque octet envoyé au clavier le clavier répond par un octet de confirmation 0xFA ou un octet demandant le renvoie 0xFE s'il n'a pas réussi à lire correctement l'octet reçu.

Le clavier envoie au MCU des scancodes et non des caractères ASCII. Le programme doit donc convertir les codes clavier en caractères. Le caractère correspondant à un code particulier dépend de la configuration du clavier, QWERTY, AZERTY, etc. Le fichier QWERTY.H contient les tables pour la transcription des codes pour un clavier du même nom. Pour utiliser un clavier avec une configuration différente il faut créer un fichier avec les tables appropriées et remplacer les #include "qwerty.h" dans le code source.

Le code source est abondamment commenté, vous devriez donc être en mesure de comprendre en le lisant.

photo du montage

  • P1.0 signal clock interface PS/2
  • P1.1 signal data interface PS/2
  • P2.2 sortie audio pour démo 3

Contrôle des LEDs du clavier

Puisque sur un clavier de PC lorsqu'on presse les touches Ver. Maj., Ver. Num. et défil. la LED correspondante sur le clavier s'allume ou s'éteint ou pourrait-être porté à croire que c'est le clavier qui contrôle l'état des LEDs mais ce n'est pas le cas, c'est le PC hôte qui en est responsable.

Après avoir programmé le MCU avec le code du démo, à la réinitialisation le MCU envoie une commande 0xFF (Remise à zéro) au clavier. Cette commande à pour effet de faire exécuter la routine d'initialisation du clavier. Cette routine s'appelle BAT Basic Assurance Test. Au démarrage du BAT les 3 LEDS s'allument simultanément. Si le test passe avec succès le clavier renvoie au MCU le code 0xAA, s'il échoue il renvoie le code 0xFC. A la fin du test les 3 LEDs s'éteignent. Si le MCU reçoit le code 0xFC il allume la LED2 sur la carte launchpad et entre en mode LPM4. En fait chaque fois qu'il y a une erreur clavier le programme entre en mode LPM4 (arrêt d'exécution).

Si le BAT passe avec succès la LED2 du launcpad allume une demi seconde et ensuite le MCU entre dans une boucle while() à l'intérieur de laquelle il envoie à tout les quart de seconde une commande au clavier avec la fonction set_kbd_leds() qui est utilisée pour contrôler l'État des LEDs du clavier. Les 3 LEDs sont allumées en séquence, une à la fois. Il suffit d'enfoncer n'importe quelle touche pour passer au démo 2.

Fonctionnement des touches à bascules

Ce démo est une démonstration de la façon dont un PC contrôle les LEDs du clavier lorsqu'on utilise les touches à bascule Ver. Maj, Ver. Num. et Défil.. En pesant sur l'une de ces touches la LED correspondante bascule entre allumée/éteinte. Pour sortir de ce démo et passer au clavier musical il faut utiliser la touche ESC.

clavier musical

Le 3ième démo est un mini synthétiseur mono tonal (un seule note à la fois). Sur un clavier QWERTY comme celui-que j'ai utilisé la rangée qui débute avec la lettre A est utiliser pour générer les notes non altérées et la rangée au dessus pour les dièses. Le A est le DO4, le W est le DO#4, le S le RÉ4, le E le RÉ#4 et ainsi de suite jusqu'au P qui est le RÉ#5. Si la touche SHIFT du côté gauche est maintenue enfoncée les notes sont transposées à l'octave supérieur. La touche F1 est utiliser en bascule pour altérer le timbre des notes. Il y a aussi un effet vibrato contrôler par les flèches, Haut et bas pour la fréquence du vibrato, et les flèches gauche et droite pour modifier la profondeur du vibrato. Le vibrato est produit en modifiant directement la fréquence du DCO.

API de l'interface clavier PS/2

  • int init_ps2_kbd() initialisation des E/S et RAZ clavier.
  • void enable_keyboard_rx() active l'interruption sur KBD_CLK.
  • void disable_keyboard_rx() désactive l'interruption sur KBD_CLK. Lorsque cette interruption est désactivée le MCU ne lit plus les codes envoyés par le clavier.
  • int get_scancode() lit le prochain code qui est dans la file d'attente. Si la file est vide retourne 0. En fait plusieurs codes peuvent-être lus car parfois un message comprend plus d'un code. Par exemple lorsqu'une touche est relâchée le message commence par 0xF0 suivit du code de la touche. Le code retourné est une entier négatif s'il s'agit d'un relâchement de la touche et positif s'il s'agit d'une touche enfoncée. Il y a aussi un bit XT_BIT qui indique s'il s'agit d'un code étendu.
  • int get_key(int code) fait la conversion du code clavier au code ASCII correspondant. S'il n'y a pas de code ASCII correspondant retourne 0. Le XT_BIT et le REL_BIT sont conservés tel qu'ils sont dans le code passé en argument.
  • int kbd_send(char cmd) est utilisée pour envoyer une commande au clavier ainsi que les arguments de la commande. Un seul octet est envoyé à la fois. Si la commandes requiert des arguments kbd_send() est invoquée autant de fois que nécessaire.
  • int set_kbd_leds(unsigned int leds_state) est utilisée pour modifier l'état des LEDs du clavier. le bit 0 de leds_state correspond au LED Défil., le bit 1 à Ver. Maj. et le bit 2 à Ver. Num. La valeur 1 dans le bit allume le LED et active la fonction correspondante.

Liens

dimanche 21 juillet 2013

msp430 launchpad, partie 8

Mise à jour 2013-07-27

Continuant mes expérimentations avec la génération d'un signal NTSC j'ai appris1 qu'il est possible de simplifier la synchronisation. Premièrement il n'est pas nécessaire de générer des lignes de demi-longueur (9 au total) 3 pulses larges, 3 pulses étroits et 3 pulses larges pour assurer la synchronisation verticale. Il suffit de générer 3 lignes avec des pulses larges (H_LINE-H_SYNC) à toute les 262 lignes. De plus au lieu de compter 262,5 lignes on peut en compter 262. Ça fonctionne tout aussi bien avec cette simplification. Voici le code de ta1_ccr0_isr simplifié. J'ai aussi modifié les constantes suivantes:
#define FIRST_LINE 30 // première ligne avec affichage
#define LAST_LINE 250 // dernière ligne avec affichage

NOTE 1) référence: Programming 32 bits microcontrollers in C, Exploring PIC32, Lucio Di Jasio, Éd. Newnes.
chapitre 13 page 339.

Génération d'un signal NTSC

Ces derniers jours j'ai expérimenter avec la génération d'un signal vidéo NTSC en noir et blanc sur le launchpad avec le processeur G2553. Le programme démo est entièrement en 'C', pas une seule ligne de code assembleur et ça fonctionne. Générer un signal NTSC sur un petit microcontrôleur est grandement simplifié grâce à la technique du progressive scan et à utilisation judicieuse de 2 périphériques, soit une minuterie en mode PWM pour générer le signal de synchronisation et d'un périphérique SPI pour la sortie du signal vidéo. Quand au circuit électronique il est des plus simple puisqu'il ne nécessite que 2 résistances. Je vais expliquer en détail comment ça fonctionne.

Code source

Standard NTSC

Le standard NTSC est le standard de la télé analogique mis au point dans les années 194x. Au départ c'était un standard pour le noir et blanc mais il a été révisé pour ajouter la couleur quelques années plus tard. Dans notre cas on n'est concerné que par la version noir et blanc. Le site internet le plus intéressant sur la description de ce standard est http://www.ntsc-tv.com. C'est grâce aux informations disponibles sur ce site que j'ai pu me débrouiller pour écrire ce programme.

Le signal vidéo NTSC est plutôt complexe, l'image est créé par 2 balayages (fields) en alternance. Un qui affiche les lignes paires et l'autre les lignes impaires. Chaque field est répété 30 fois par seconde. Chaque field est constitué de 262,5 lignes pour un total de 525 lignes. A cause de la persistance rétinienne on a l'impression que les 525 lignes sont affichées simultanément alors que ce n'est pas le cas. Ce système permet de réduire la bande passante du signal mais ça complique le système de synchronisation de l'image. Heureusement il est possible d'ignorer cette alternance paire/impaire et d'envoyer toujours le même field. On ne dispose alors que de 262 lignes d'affichage qui se répètent 60 fois par seconde. De ces 262 lignes 20 sont réservées pour la synchronisation verticale et donc indisponible pour l'affichage. Il ne reste donc que 240 lignes d'affichage soit la moitié d'un signal VGA.

Le circuit électronique est très simple il suffit d'une résistance de 470 ohm branchée sur P1.2 (sortie PWM) pour la synchronisation et d'une autre résistance de 220 ohm branchée sur la sortie P1.7 (sortie SIMO du SPI) pour le signal vidéo. Il n'y a pas de nuances de gris, c'est vraiment du noir et blanc. Les deux résistances sont jointent ensemble et ce point de jonction est la sortie vidéo qui va à l'entrée NTSC du téléviseur.

Configuration des périphériques

La partie synchronisation du signal est générée par PWM grâce à la minuterie TIMER_A1 configurée en mode comparateur avec comptage UP. TA1CCR0 détermine la période qui correspond à la durée d'une seule ligne vidéo soit 62,5µSec. Le DCO étant programmé pour fonctionner à 16Mhz, la période de cycle du SMCLK est de 62,5nSec, ce qui donne 1000 cycles par ligne. De ces 62,5µSec 10,5µSec sont réservés pour l'impulsion de synchronisation plus les seuils avant et arrière. Il reste donc 52µSec disponible pour l'affichage vidéo. Ce qui complique les choses est la synchronisation verticale. A toute les 262,5 lignes il faut générer une série d'impulsions dont la durée varie. 3 courtes, 3 longues et 3 courtes. Ce travail est accomplie dans la sous-routine d'interruption du compteur de période CCR0, appelée dans le programme ta1_ccr0_isr. le registre CCR1 est configuré pour déterminer la longueur de l'impulsion de synchronisation et CCR2 pour la durée du seuil. lorsque le compteur TA1R arrive à la valeur de CCR2 l'ISR ta1_ccrx_isr est appelée et celle-ci est responsable de l'affichage de l'image qui est contenu dans le tableau frame[56][7]. La résolution de l'image n'est que de 56x56 pixels du à la quantité limitée de mémoire RAM qui n'est que de 512 octets. De ces octets une certaine quantité est réservée pour la pile et les autres variables.

L'astuce intéressante est la façon d'utiliser un périphérique SPI pour sortir les bits vidéos. Le périphérique est configuré en mode SPI maître mais au lieu de brancher la sortie SIMO qui normalement envoie les octets vers un périphérique SPI esclave, on s'en sert comme sortie vidéo. le SPI et cadencé par le signal SMCLK divisé par 13. Ce diviseur et calculé pour que les 56 bits par ligne soient envoyées en un temps inférieur à 52µSec. 52µSec/56/62,5nSec= 14,8. J'ai utilisé 13 comme diviseur pour laissé une marge à la fin de la ligne pour être certain que la routine ISR se termine avant l'interruption de fin de ligne. Plus le diviseur est petit plus les pixels sont comprimés horizontalement. Chaque ligne du tableau frame est répétée 4 fois ce qui donne 4*56=224 lignes ce qui nous laisse aussi une marche verticalement.

A titre de démo je fais simplement défilé un texte au centre de l'écran.

Conclusion

Avec un affichage de 56x56 pixels noir/blanc on ne peut pas faire grand chose mais j'ai une idée sur la façon d'augmenter la mémoire vidéo. Puisqu'on utilise un périphérique SPI pour sortir les bits vidéo il serait simple d'utiliser une mémoire RAM externe à interface SPI comme la Microchip 23K256. Avec 32K de RAM SPI on a de la place pour plusieurs buffer de 5760 octets (192x240pixels). On interface la RAM avec le MCU de manière conventionnel mais on branche la sortie SOMI à l'entrée NTSC à travers une résistance de 220 ohm. Donc il suffit que le MCU fasse la lecture de la RAM SPI pour que les bits soient envoyés sur la sortie vidéo. Avec un tel système on pourrait obtenir une résolution vidéo de 320x240.

quelques liens intéressants sur ce sujet

Une image en FLASH affichée avec une résolution de 192x240.
Standard NTSC.
Exemple du jeux pong fourni par Texas Intruments slaa177.

lundi 15 juillet 2013

msp430 launchpad, partie 7

Contrôle d'un servo-moteur

Sujets traités:

  • Ajustement fin de la fréquence du DCO
  • Utilisation d'une minuterie pour contrôler un servo-moteur

Dans cet article je traite de 2 sujets, le premier consiste à ajuster la fréquence du DCO pour obtenir une fréquence en dehors des calibrations fournis par TI. Le deuxième consiste à utiliser une minuterie pour générer le signal PWM qui contrôle le servo-moteur. Le programme démo consiste à lire le voltage d'un potentiomètre branché sur P1.0 et à faire en sorte que la position du servo-moteur suive la position du potentiomètre. Les impulsions qui contrôle le servo sont sortie sur P2.1.

schéma du circuit

La rotation du servo-moteur suis la rotation de RV1.

Configuration de la minuterie

Les registres spéciaux utilisés par une minuterie de type A sont les suivants:

  • TAxCTL fonctionnement général de la minuterie, choix de l'horloge du diviseur, etc.
  • TAxR c'est le compteur 16 bits utilisé par la minuterie.
  • TAxCCTLy registre de contrôle du mode capture ou comparateur pour le registre CCRy
  • CCRy registre qui contient la valeur de comparaison avec TAxR
  • TAxIV registre identifiant la source de l'interruption puisqu'un seul vecteur partage 3 interruptions.
NOTE: 'x' identifie la minuterie 0 ou 1. 'y' identifie le registre comparateur 0,1,2.

Le programme utilise la minuterie no. 1 (TA1). Comme expliqué précédemment chaque minuterie à 3 registres de comparaisons CCRy. Les étapes de configurations d'une minuterie sont les suivantes:
configuration générale de la minuterie par le registre TA1CTL

  1. Choisir le signal source horloge qui va contrôler la minuterie.
  2. Choisir un diviseur pour ce signal.
  3. Choisir le mode de contrôle de la minuterie:
    1. STOP la minuterie est arrêté.
    2. UP TAxR est incrémenté jusqu'à la valeur de CCR0 et recommence à zéro. CCR0 détermine donc la période=Tclk*CCR0
    3. CONTINUOUS TAxR est incrémenté jusqu'à la valeur maximale 0xFFFF et recommence à zéro. Période fixe de longueur maximale=Tclk*65536.
    4. UP/DOWN TAxR est incrémenté jusqu'à la valeur de CCR0 et ensuite décrémenté vers zéro. Période=Tclk*2*CCR0
  4. Choisir s'il y aura une interruption sur débordement du compteur.

Ensuite pour chacun des registres CCRy il y a un registre à configurer TA1CCTLy
  1. Type d'utilisation capture ou comparateur
  2. Pour le mode comparaison choisir le type de sortie. C'est à dire ce qui arrive lorsque le compteur atteint la valeur qui est dans CCRy
    • Mode 0 output, la valeur de la broche de sortie est déterminée par le bit OUTx du registre TAxCCTLy.
    • Mode 1 set, lorsque TAxR atteint CCRy la sortie passe à 1 et y reste jusqu'à un reset du MCU ou un changement de configuration du mode minuterie.
    • Mode 2, Toggle/Reset, la sortie est inversée lorsque TAxR==CCRy et remise à zéro lorsque TAxR==CCR0 (fin de période).
    • Mode 3, Set/Reset, la sortie est mise à 1 lorsque TAxR==CCRy et remise à zéro lorsque TAxR==CCR0.
    • Mode 4, Toggle, la sortie est inversée à chaque fois que TAxR==CCRy. La période du signal de sortie est donc le double de la période du compteur TAxR.
    • Mode 5, Reset, la sortie est mise à zéro lorsque TAxR==CCRy et y reste jusqu'à réinitialisation du MCU ou reprogrammation de la minuterie.
    • Mode 6, Toggle/Set, la sortie est inversée lorsque TAxR==CCRy et est mise à 1 lorsque TAxR==CCR0, on obtient donc un signal complémentaire à celui du mode 2.
    • Mode 7, Reset/Set, la sortie est mise à zéro lorsque TAxR==CCRy et mise à 1 lorsque TAxR==CCR0, on obtient donc un signal complémentaire du mode 3.
  3. Choisir si on veut déclencher une interruption lorsqu'il y a égalité TAxR==CCRy

Code source

Description

La minuterie TA1 est alimenté par SMCLK et fonctionne en mode UP. La sortie est en mode reset/set ce qui donne un signal inverse de celui utilisé par le servo-moteur. Ceci est nécessaire car le transistor Q1 va réinverser ce signal de sorte que le la ligne de contrôle du servo-moteur aura le bon signal, soit une série d'impulsions d'une durée qui varie de 400µsec à 2400µsec se répétant à toutes les 20millisecondes.

Le périphérique ADC10 est utilisé pour lire la valeur du potentiomètre branché sur P1.0. A chaque période du signal servo, une série de 8 lectures du potentiomètre est faite et la moyenne est utilisée pour contrôler la largeur de l'impulstion PWM du servo.

Quelques calculs simples

Le servo-moteur utilisé (Hitec HS-420) est conçu pour être contrôlé par une série d'impulsions dont la largeur minimale est de 400µsec et la largeur maximal de 2400µsec. La valeur centrale est de 1500µsec. Ces impulsions doivent se répétées à toutes les 20millisecondes. Pour que le PWM puisse utiliser les 16 bits du compteur TA1R il faut que la fréquence qui alimente TA1R soit exactement de 65536*Tclk. Où Tclk est la période du signal SMCLK. On obtient donc que le DCO doit fonctionner à 3,28Mhz. Dans le manuel du MSP430G2553 il y a une table avec les valeurs qui servent à configurer les registres DCOCTL et BCSCTL1 pour obtenir la fréquence désirée. Notez que ces valeurs sont approximatives et ne servent qu'à titre indicatif. A partir de cette table DCO frequency j'ai utilisé les valeurs RSELx=10 (range select), DCOx=2 (step selector) et en utilisant un oscilloscope j'ai mesuré la fréquence de sortie du signal PWM sur P2.1. J'ai ajusté la valeur MODx (modulator) pour obtenir 50Hz.

Pour calculer les valeurs MIN_PULSE, MAX_PULSE et CENTER_PULSE il s'agit d'une simple règle de 3.
N=pulse_width/20000*65536
N valeur à mettre dans TA1CCR1.
pulse_width largeur de l'impulsion en µsec.
20000 période PWM en µsec.

la ligne de code suivante nécessite peut-être une explication:

TA1CCR1=MIN_PULSE+(v_mean>>1)+(v_mean>>2);

v_mean contient la somme des 8 échantillons ADC10. Pour obtenir la valeur moyenne il faut diviser cette valeur par 8. Mais je dois multiplier cette valeur moyenne par 6 pour ajuster correctement la valeur à mettre dans TA1CCR1. donc au lieu de divisé par 8, je divise par 2 et par 4 et j'additionne les 2 valeurs ainsi obtenues1. Le résultat est le même mais j'ai éviter l'utilisation de routines de multiplication et division qui auraient occupées plus d'espace de code et seraient plus lentes d'exécution.
NOTE 1: multiplié par 6 = *4 + *2 et diviser par 8 pour ensuite multiplier par 4 est idem à diviser par 2...

Quant à la valeur 6 il s'agit simplement du rapport entre l'intervalle des lectures ADC10 (1023) et de l'intervalle MAX_PULSE - MIN_PULSE. Donc en prenant la valeur lu par ADC10 puis en la multipliant par 6 et en ajoutant à MIN_PULSE, la largeur des impulsions est maintenue dans les spécifications du servo-moteur.