Développement, électronique et CNC - Mot-clé - USB2023-04-18T12:14:31+02:00Eric Cocquerezurn:md5:19fe27ec1d886d7d240825ca5a32e357DotclearMVVM, USB et HIDurn:md5:4fd453424c277e0a0a98aa22fdeaadc32011-08-09T18:39:00+01:002011-08-31T10:16:23+01:00Eric CocquerezPIC MicrochipUSBVisual Studio<p>Sous ce titre énigmatique se cache un petit projet pédagogique mettant en œuvre un ensemble de technologies. De l'utilisation d'une sonde de température DS1820 communiquant avec un micro-contrôleur PIC18F2550 selon le protocole 1-Wire, puis de la communication USB selon le protocole HID (Human Interface Device) afin de notifier un programme développé en C# sous Visual Studio 2010 en appliquant le pattern MVVM</p> <p>Sous ce titre énigmatique, je vais vous décrire un petit projet, parfaitement inutile, mais pédagogique (il faut bien trouver une raison) mettant en œuvre un ensemble de technologie.</p>
<ul>
<li>Une jolie sonde de température, de type <a href="https://www.maxim-ic.com/datasheet/index.mvp/id/3021">DS1820</a>, discute en utilisant le protocole 1-Wire avec un micro-contrôleur <a href="https://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en010280">PIC 18F2550</a>.</li>
<li>Ce petit micro-contrôleur comporte une interface USB permettant d'implémenter le protocole HID. Il peut donc communiquer avec un Micro Ordinateur sans avoir à utiliser un driver spécial.</li>
<li>Enfin, ultime étape de ce projet, afficher la température en utilisant un petit programme écrit en C# sous Visual Studio 2010 (Personne n'est parfait :-) .<br /></li>
</ul>
<pre></pre>
<p>Pour corser un peu la dernière étape, nous allons utiliser le pattern MVVM, en utilisant le framework fournit par Mr Laurent Bugnion : <a href="https://www.galasoft.ch/mvvm/">MVVM Light ToolKit</a></p>
<h3>Circuit électronique</h3>
<h4>Schéma de base pour le micro-contrôleur</h4>
<p>Avant de faire du logiciel, il faut faire un minimum de Hardware. Le schéma de la carte est très simple et reprends les spécifications de base des différents datasheet. Le circuit est alimenté en 5 Volts au travers de la connexion USB.<br />
On utilise un quartz de 20MHz avec 2 petites capacités de 22pF. Le circuit de Reset est également classique, une résistance de tirage et une capacité.<br />
Il faut ajouter une capacité sur la broche Vusb du micro-contrôleur. Il semblerait qu'il faille un minimum de 220nF alors que la documentation indique 100 nF<br />
On ajoute également la très classique capacité de 100nF sur l'alimentation permettant de filtrer tous les petits parasites.</p>
<h4>Interface de sonde</h4>
<p>La connexion de la sonde est on ne peut plus simple, il suffit de l'alimenter et de relier la borne de données à un des ports du micro-contrôleur. Il faut juste ajouter une résistance de tirage haut de 4,7 KOhms.<br /></p>
<h4>Schéma du montage <br /></h4>
<p><a href="https://cocquerez.com/public/electronique/schema.PNG" title="schema.PNG"><img src="https://cocquerez.com/public/electronique/.schema_m.jpg" alt="schema.PNG" style="display:block; margin:0 auto;" title="schema.PNG, août 2011" /></a></p>
<h4>le circuit imprimé fortement agrandi : <br /></h4>
<p><a href="https://cocquerez.com/public/electronique/circuite.PNG" title="circuite.PNG"><img src="https://cocquerez.com/public/electronique/.circuite_m.jpg" alt="circuite.PNG" style="display:block; margin:0 auto;" title="circuite.PNG, août 2011" /></a></p>
<h4>le résultat final : <br /></h4>
<p><a href="https://cocquerez.com/public/electronique/temperature1.jpg" title="temperature1.jpg"><img src="https://cocquerez.com/public/electronique/.temperature1_m.jpg" alt="temperature1.jpg" style="display:block; margin:0 auto;" title="temperature1.jpg, août 2011" /></a></p>
<h4>une autre vue : <br /></h4>
<p><a href="https://cocquerez.com/public/electronique/temperature2.jpg" title="temperature2.jpg"><img src="https://cocquerez.com/public/electronique/.temperature2_m.jpg" alt="temperature2.jpg" style="display:block; margin:0 auto;" title="temperature2.jpg, août 2011" /></a></p>
<p>Les schémas et routage de ce circuit ont été réalisé en utilisant le logiciel <a href="https://www.lis.inpg.fr/realise_au_lis/kicad/">Kicad</a>
J'ai ensuite simplement imprimé le typon sur une imprimante laser, insoler et graver le circuit avec une solution d'eau, d'acide chlorhydrique et d'eau oxygénée à 110°</p>
<h3>Le code du micro-contrôleur, utilisation du stack USB HID et mise en oeuvre du protocole One Wire</h3>
<h4>Environnement de développement</h4>
<p>En utilisant des PICs, je pense que le plus simple est d'utiliser les outils mis gracieusement à disposition par <a href="https://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=81">Microchip</a>. J'utilise donc l'IDE MPLAB ainsi que le compilateur C18 dernière version.<br />
Partant du principe qu'il est inutile de tout refaire, je me suis basé d'un exemple fourni dans le <a href="https://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=2651&param=en534494">stack USB</a>.</p>
<h4>Traitement du OneWire</h4>
<p>La communication avec un composant OneWire n'est pas compliqué. Tout d'abord, quelques #define afin de faciliter la lecture du code et permettre également de l'adapter plus rapidement : <br />
Définition du port utilisé pour communiquer avec le composant <br /></p>
<p><code>#define OneWireOut LATBbits.LATB2</code><br />
<code>#define OneWireIn PORTBbits.RB2</code><br />
<code>#define OneWireControl TRISBbits.TRISB2 </code><br /></p>
<p>Avant d'interroger le composant, il faut d'abord vérifier qu'il soit présent. Cette opération est réalisée par cette petite fonction : <br /></p>
<p><code>//Fonction de reset et test de presence, return, 1 si un device est présent sur la ligne</code><br />
<code>unsigned char ResetAndPresence(void)</code><br />
<code>{</code><br />
<code> unsigned char byPresent = 0;</code><br />
<code> //Port en sortie</code><br />
<code> OneWireControl = 0;</code><br />
<code> //On force un 0 en sortie</code><br />
<code> OneWireOut = 0;</code><br />
<code> //Attente du délai minimum</code><br />
<code> Wait480us();</code><br />
<code> //On force le port à 1</code><br />
<code> OneWireOut = 1;</code><br />
<code> //On repasse le port en entrée</code><br />
<code> OneWireControl = 1;</code><br />
<code> //Attente du temps minimum (entre 15 et 60 us pour la réponse), On attend 70us</code><br />
<code> Wait70us();</code><br />
<code> if(OneWireIn == 0) </code><br />
<code> {</code><br />
<code> byPresent = 1;</code><br />
<code> }</code><br />
<code> Wait410us();</code><br />
<code> OneWireControl = 0;</code><br />
<code> OneWireOut = 1;</code><br />
<code> return byPresent;</code><br />
<code>}</code><br /></p>
<p>Comme on peut facilement le constater, cette fonction retourne 1 si le composant est présent, sinon, elle retourne 0.<br />
Les différentes fonctions d'attentes sont disponible dans le fichier <a href="https://www.cocquerez.com/public/1wire.c">source</a></p>
<p>Il y a également plusieurs petites fonctions de base :<br /></p>
<h5>Ecrire un 1 sur le port de communication <br /></h5>
<p><code>void Writebit1(void)</code><br />
<code>{</code><br />
<code> //On force un 0 en sortie</code><br />
<code> OneWireOut = 0;</code><br />
<code> //Attente du délai minimum</code><br />
<code> Wait10us();</code><br />
<code> OneWireOut = 1;</code><br />
<code> Wait60us();</code><br />
<code>}</code><br /></p>
<h5>Ecrire un 1 sur le port de communication <br /></h5>
<p>J'aurais pu factoriser un peu ces 2 fonctions d'écriture, mais vu le code, je n'y ai pas trouvé trop grand intérêt. De plus la place disponible sur ce micro-contrôleur étant plus que surdimensionné pour ce projet, j'ai préféré faire des fonctions basiques.<br /></p>
<p><code>void Writebit0(void)</code><br />
<code>{</code><br />
<code> //On force un 0 en sortie</code><br />
<code> OneWireOut = 0;</code><br />
<code> //Attente du délai minimum</code><br />
<code> Wait60us();</code><br />
<code> OneWireOut = 1;</code><br />
<code> Wait10us();</code><br />
<code>}</code><br /></p>
<h5>Lire 1 bit le port de communication <br /></h5>
<p><code>unsigned char ReadBit(void)</code><br />
<code>{</code><br />
<code> unsigned char ucRet = 0;</code><br />
<code> //On force un zero pendant un minimum de 1us</code><br />
<code> OneWireOut = 0;</code><br />
<code> Wait2us();</code><br />
<code> //On repasse en entrée</code><br />
<code> OneWireControl = 1;</code><br />
<code> Wait2us();</code><br />
<code> Wait2us();</code><br />
<code> Wait2us();</code><br />
<code> if(OneWireIn == 1) ucRet = 0x01;</code><br />
<code> //Attente du minimum syndical et reprise du bus</code><br />
<code> Wait50us();</code><br />
<code> Wait10us();</code><br />
<code> OneWireControl = 0;</code><br />
<code> OneWireOut = 1;</code><br />
<code> return ucRet;</code><br />
<code>}</code><br /></p>
<p>Du coup, les fonctions de plus haut niveau sont relativement simples.<br /></p>
<h5>Ecriture d'un Byte sur le port.<br /></h5>
<p>Cela se résume à une simple histoire de rotation<br />
<code>void WriteByte(unsigned char Data)</code><br />
<code>{</code><br />
<code> unsigned char byBoucle = 0;</code><br />
<code> for(byBoucle = 0; byBoucle < 8; byBoucle++)</code><br />
<code> {</code><br />
<code> if((Data & 0x01) == 0x01) Writebit1();</code><br />
<code> else Writebit0();</code><br />
<code> Data >>= 1;</code><br />
<code> }</code><br />
<code>}</code><br /></p>
<h5>Lecture d'un Byte<br /></h5>
<p>Là encore, une simple histoire de rotation<br /></p>
<p><code>unsigned char ReadByte(void)</code><br />
<code>{</code><br />
<code> unsigned char byBoucle = 0;</code><br />
<code> unsigned char ucRet = 0x00;</code><br />
<code> for(byBoucle = 0; byBoucle < 8; byBoucle++)</code><br />
<code> {</code><br />
<code> ucRet >>= 1;</code><br />
<code> if(ReadBit() == 0x01) ucRet |= 0x80;</code><br />
<code> }</code><br />
<code> return ucRet;</code><br />
<code>}</code><br /></p>
<h4>Gestion de la communication USB</h4>
<h3>Développement de l'interface graphique sous Visual Studio 2010 en MVVM</h3>
<p>Pour des besoins professionnels, je me suis mis à développer en C# en utilisant le pattern MVVM (Model-Vue- VueModel). Ce projet professionnel étant un peu compliqué, je me suis dit qu'utiliser ce même pattern pour le développement de cet interface constituerait un excellent exercice.<br />
Le choix du toolkit MVVM a été relativement simple, il existe Prism de Microsoft et un Framework beaucoup plus accessible fournit par Mr Laurent Bugnion : <a href="https://mvvmlight.codeplex.com/">MVVM Light Toolkit</a>. Ce Framework a d'ailleurs été choisi par la société dans laquelle je travaille pour développer un nouveau projet<br />
L'installation est simple, il suffit de lire et de suivre le mode d'emploi. La création du projet C# WPF se fait simplement en sélectionnant le template adéquat.<br /></p>
<p>Dans ce nouveau projet, on crée le dossier Model, puis commence le premier problème, discuter en USB avec la sonde de température. Après avoir fouiné un peu sur le net, et après plusieurs essais, j'ai décidé d'utiliser la <a href="https://www.libusb.org/wiki/libusb-win32">Libusb-win32</a>, projet qui me semble être le mieux abouti.<br />
Les exemples sont simples et permettent rapidement de prendre en main cette librairie.<br />
La classe de communication avec le PIC18F2550 ne pose donc pas gros problème et j'obtiens rapidement la mesure de la température.<br /></p>
<p>Un problème plus conceptuel se pose alors. Le PIC8F2550 envoie de façon périodique les informations. Ce périphérique est aussi un périphérique USB, et peut donc être retiré ou inséré alors que l'ordinateur est en fonction. Il faut donc que le Modèle puisse avertir le "Vue-Modèle". Par principe, je n'aime pas les timers pour effectuer des interrogations périodiques. <br />
Me revoilà donc reparti à la recherche de renseignements sur ce pattern et je trouve la solution en lisant ce <a href="https://www.e-naxos.com/Blog/post/2010/08/08/Appliquer-la-pattern-MVVM-avec-MVVM-Light.aspx">blog</a>. Il suffit de télécharger le fichier zip et de lire le document et de consulter les différents exemples. Ils sont très simple et formateur.<br />
Selon ce pattern, et en utilisant les fonctionnalités du Framework, il est tout à fait possible de communiquer entres les différentes parties en envoyant des messages. Concernant la détection d'insertion ou de retrait de la sonde de température, il aurait été très simple d'implémenter la fonctionnalité dans le "Vue-Modèle", mais cela entrainerait une liaison forte alors que l'implémentation de cette détection dans le modèle et l'envoi d'un message en utilisant les fonctionnalités du Framework n'entraine pas de forte liaison et permet en plus de notifier le "Vue-Modèle" de la réception d'un nouveau message de température. IL n'y aura donc pas de pooling pour récupérer les données.<br /></p>
<p>La vue est on ne peut plus simple. Les informations essentielles sont "Bindées" et l'affichage se met à jour automatiquement.<br />
Une petite zone de texte permet de vérifier que le Binding fonctionne correctement, un autre champ indique si une sonde de température est connectée.<br />
La température est affichée directement et une petite étoile tourne de 10° à réception d'un message.<br /></p>
<p>Le code source de cette application est disponible <a href="https://www.cocquerez.com/public/HidTempLight.zip">ici</a>.</p>
<h3>Le résultat final : <br /></h3>
<p><img src="https://cocquerez.com/public/electronique/interface.png" alt="interface.png" style="display:block; margin:0 auto;" title="interface.png, août 2011" />
<img src="https://cocquerez.com/images/electronique/interface.png" alt="" /></p>
<p>Je suis désolé, j'ai fait les schémas électronique, le routage, la gravure du circuit, la codage du micro-contrôleur, le logiciel sous Visual Studio 2010 en C#, j'en ai profité pour apprendre et mettre en oeuvre le ToolKit MVVM Light, mais définitivement, je ne serais jamais un graphiste ou un designeur.</p>Utilisation du stack USB CDC avec le PIC18F2550urn:md5:170c53abbd47e94f586d4d18f4c98a0c2010-02-02T09:30:00+00:002011-08-30T12:01:49+01:00Eric CocquerezPIC MicrochipUSB<p>Ayant eu envie de regarder le fonctionnement du bus <a href="https://www.maxim-ic.com/" hreflang="fr">1 Wire</a> et en particulier des sondes de températures de type <a href="https://datasheets.maxim-ic.com/en/ds/DS18S20.pdf">DS18S20</a>, je me suis trouvé devant le problème d'afficher les différents résultats.<br />
N'ayant pas envie d'utiliser un afficheur LCD, je suis parti sur l'idée d'utiliser le stack <a href="https://www.microchip.com/usb">USB</a> gracieusement ...</p> <p>Ayant eu envie de regarder le fonctionnement du bus <a href="https://www.maxim-ic.com/" hreflang="fr">1 Wire</a> et en particulier des sondes de températures de type <a href="https://datasheets.maxim-ic.com/en/ds/DS18S20.pdf">DS18S20</a>, je me suis trouvé devant le problème d'afficher les différents résultats.<br />
N'ayant pas envie d'utiliser un afficheur LCD, je suis parti sur l'idée d'utiliser le stack <a href="https://www.microchip.com/usb">USB</a> gracieusement fourni par Microchip pour afficher les informations sur mon écran de PC.
Le stack permet de choisir plusieurs modes de fonctionnement, le plus simple et le plus rapide à mettre en œuvre étant d'implémenter la partie CDC et d'afficher ainsi les résultats sur un simple écran "Hyper Terminal".</p>
<h3>Configuration du projet.</h3>
<p>Afin de simplifier les explications, j'utilise la suite d'outils Microchip (MPLAB IDE, C18 compileur) installée avec les paramètres et dossiers par défauts.<br />
A l'origine, j'avais juste un fichier main.c contenant l'initialisation et la boucle principale du programme, et de deux fichiers (1wire.c et 1wire.h) pour la gestion de la communication One Wire.<br />
J'ai commencé les tests en allumant une led si je détectai une sonde, et l'éteignai si je la déconnectai. J'ai vite rencontré le problème pour afficher l'identifiant de la sonde, ou même afficher la température.<br />
Pour information, j'ai rencontré un soucis lors des premiers tests. J'avais ajouté les fichiers dans l'environnement de travail, mais j'avais oublié d'ajouter la directive <code>#include "1wire.h"</code> dans le fichier main.c. Le compilateur ne semblait absolument pas troublé par cet oubli, mais le programme ne fonctionnait pas. Le simple fait de corriger cette erreur a permit la détection de la sonde.</p>
<h4>Ajout des chemins et fichiers nécessaires dans l'environnement de travail.</h4>
<p>Pour créer un projet de base, il faut ajouter les fichiers sources et le fichier linker (.lkr) correspondant au type de micro contrôleur que vous allez utiliser.<br /><br /></p>
<p><a href="https://cocquerez.com/public/electronique/project.png" title="project.png"><img src="https://cocquerez.com/public/electronique/.project_m.jpg" alt="project.png" title="project.png, août 2011" /></a></p>
<p>N'oubliez pas de définir le chemin vers les librairies afin que le compilateur puisse trouver le fichier c018.o<br /><br /></p>
<p><img src="https://cocquerez.com/public/electronique/.lib_m.jpg" alt="lib.png" style="display:block; margin:0 auto;" title="lib.png, août 2011" /><br /></p>
<p>Par défaut, les fichiers de définitions du stack USB ne sont pas définis dans l'environnement. IL faut alors sélectionner l'option de menu "Projet", puis "Build Options" et sélectionner le projet.<br />
Il faut ensuite ajouter le chemin pour trouver les fichiers include du stack USB<br /><br /></p>
<p><img src="https://cocquerez.com/public/electronique/.include_m.jpg" alt="include.png" style="display:block; margin:0 auto;" title="include.png, août 2011" />
<br /></p>
<h4>Ajout des fichiers des fonctions CDC.</h4>
<p>Il suffit d'inclure le fichier *** dans le projet.</p>
<h4>Construction des fichiers usb_config.c et usb_config.h.</h4>
<p>Microchip fournit avec le stack un utilitaire permettant de construire ces deux fichiers permettant de configurer la connexion USB. Voici les copies d'écrans de ma configuration.<br />
<strong>Attention</strong>Il faut ensuite créer un fichier device_descriptor.c décrivant les structures ....<br />Pour l'instant, j'ai simplement récupérer celui d'un exemple.
Il faut également créer un fichier "hardwareprofile.h" car il est systématiquement inclus dans les diverses sources fournies par <a href="https://www.microchip.com">Microchip.</a><br /></p>
<h4>Descriptions des registres de configuration.</h4>
<p>Voici donc la configuration que j'utilise pour cette plaque de test :<br /></p>
<p><code>#pragma config PLLDIV = 5 //Quartz à 20MHz, on le divise par 5 pour avoir 4 MHz pour alimenter la PLL</code><br />
<code>#pragma config CPUDIV = OSC1_PLL2 </code><br />
<code>#pragma config USBDIV = 2 // Clock source from 96MHz PLL/2, en interne, je tourne à 48 MHz</code><br />
<code>#pragma config FOSC = HSPLL_HS</code><br />
<code>#pragma config FCMEN = OFF</code><br />
<code>#pragma config IESO = OFF</code><br />
<code>#pragma config PWRT = OFF</code><br />
<code>#pragma config BOR = ON</code><br />
<code>#pragma config BORV = 3</code><br />
<code>#pragma config VREGEN = ON //USB Voltage Regulator</code><br />
<code># pragma config WDT = OFF</code><br />
<code>#pragma config WDTPS = 32768</code><br />
<code>#pragma config MCLRE = ON</code><br />
<code>#pragma config LPT1OSC = OFF</code><br />
<code>#pragma config PBADEN = OFF</code><br />
<code>#pragma config STVREN = ON</code><br />
<code>#pragma config LVP = OFF</code><br /></p>
<h4>Utilisation de la classe CDC</h4>
<p>En tout premier lieu, il faut appeler la fonction USBDeviceInit().<br />Cette fonction est en général appelé dans la fonction d'initialisation de votre programme.<br />
Dans la boucle principale de votre programme, il faut appeler périodiquement la fonction USBDeviceTasks().<br />Je travaille en mode polling. Tant que la variable USBDeviceState n'a pas la valeur CONFIGURED_STATE, vous ne pouvez pas utiliser les fonction USB.Avant de lire ou écrire quelque chose, il faut appeler la fonction mUSBUSARTIsTxTrfReady().<br /></p>
<p>Un exemple valant mieux qu'un long discours, voici un exemple de programme : <br /></p>
<pre><code> while(1)</code>
<code> {</code>
<code> USBDeviceTasks();</code>
<code> cPresent = ResetAndPresence();</code>
<code> //Tant que l'usb n'est pas configuré, on repart au début de la boucle</code>
<code> if((USBDeviceState < CONFIGURED_STATE )||( USBSuspendControl == 1)) continue;</code>
<code> if( mUSBUSARTIsTxTrfReady() )</code>
<code> {</code>
<code> cCompt = getsUSBUSART(USB_Out_Buffer,64);</code>
<code> //On a lu au moins 1 caratère </code>
<code> if(cCompt != 0)</code>
<code> {</code>
<code> switch(USB_Out_Buffer[0])</code>
<code> {</code>
<code> case 'p':</code>
<code> case 'P':</code>
<code> cPresent = ResetAndPresence();</code>
<code> if(cPresent == 1)</code>
<code> {</code>
<code> putrsUSBUSART("Device Ready\r\n");</code>
<code> }</code>
<code> else</code>
<code> {</code>
<code> putrsUSBUSART("Device not present\r\n");</code>
<code> }</code>
<code> break;</code>
<code> case 'c':</code>
<code> case 'C':</code>
<code> ResetAndPresence();</code>
<code> SkipRomCommand();</code>
<code> StartConvert();</code>
<code> putrsUSBUSART("Convertion\r\n");</code>
<code> break;</code>
<code> case 'r':</code>
<code> case 'R':</code>
<code> ResetAndPresence();</code>
<code> ReadRom();</code>
<code> for(cCompt = 0; cCompt < 8; cCompt++)</code>
<code> {</code>
<code> OneWireRead[cCompt] = ReadByte();</code>
<code> }</code>
<code> //On affiche les 2 premiers bytes</code>
<code> sprintf(USB_In_Buffer,"%#04x %#04x\r\n",OneWireRead[0],OneWireRead[1]); </code>
<code> putsUSBUSART(USB_In_Buffer);</code>
<code> break;</code>
<code> case 't':</code>
<code> case 'T':</code>
<code> ResetAndPresence();</code>
<code> SkipRomCommand();</code>
<code> WriteByte(0xBE);</code>
<code> for(cCompt = 0; cCompt < 9; cCompt++)</code>
<code> {</code>
<code> OneWireRead[cCompt] = 0x31;</code>
<code> OneWireRead[cCompt] = ReadByte();</code>
<code> }</code>
<code> //On affiche les 2 premiers bytes </code>
<code> sprintf(USB_In_Buffer,"%#04x %#04x n",OneWireRead[0],OneWireRead[1]);</code>
<code> putsUSBUSART(USB_In_Buffer);</code>
<code> break;</code>
<code> default:</code>
<code> putrsUSBUSART("Unknow command\r\n");</code>
<code> break;</code>
<code> }</code>
<code> }</code>
<code> }</code>
<code> //Il faut toujours appeler cette fonction</code>
<code> CDCTxService();</code>
<code> }</code></pre>