domodom
Accueil du site > mini2440 > Windows CE > Tutoriaux > Un driver SPI pour la mini2440

Un driver SPI pour la mini2440

samedi 6 mars 2010, par Dom

J’ai eu besoin, pour un projet, d’un nombre d’entrées/sorties conséquent. J’ai décidé d’utiliser des expander d’IO sur bus SPI. Ca tombe bien, le S3C2440 (processeur qui équipe la mini2440) intègre deux interfaces SPI. Par contre, il n’y a pas de driver SPI dans le BSP de la mini2440... Je vais donc le développer moi-même.

Remarque : le processeur peut fonctionner en maître ou en esclave sur le bus SPI. Le driver que je vais développer ne va supporter que le mode maître.

SPI sur S3C2440

J’ai commencé par regarder comment le S3C2440 gère le bus SPI en lisant le datasheet (téléchargeable ici) Le S3C2440 intègre 2 interfaces SPI, le fonctionnement est le même pour chacune. La gestion du bus SPI passe par les registres suivants :
- SPCONx : configuration du module SPI du processeur
- SPSTAx : état du module SPI
- SPPINx : configuration de la pin nSS
- SPPREx : configuration de la vitesse de transmission utilisée
- SPTDATx : registre d’émission
- SPRDATx : registre de réception

Mon but ici n’est pas d’expliquer toutes les subtilités du fonctionnement du bus SPI sur le S3C2440, pour faire court, disons qu’un transfert SPI va suivre les étapes suivantes :
1. configuration de la pin nSS : en mode maître, rien à faire, on laisse les valeurs par défaut.
2. configuration du module SPI : voir paragraphe suivant.
3. activation du composant SPI avec lequel on veut communiquer (= mise à 0 du signal CS).
4. attente que le module soit prêt avant d’émettre (=que le bit REDY vaille 1 avant d’écrire l’octet à transmettre dans le registre SPTDATx). La transmission commence dès l’écriture de l’octet en question.
5. attente de la fin de transmission (=que le bit 1 du registre SPCONx passe à 1)
6. récupération de l’octet reçu dans le registre SPRDATx.
7. désactivation du composant SPI avec lequel on veut communiquer (= mise à 1 du signal CS).

Quelques explications sur la configuration du module SPI.
- SPIMOD = 0 ou 1 suivant qu’on veut, lors d’un transfert, attendre la fin du transfert via une interruption (bien) ou en lisant en boucle un bit dans un registre (pas bien)
- ENSCK = 1 : on active l’horloge (je n’ai pas trop cherché à quoi peut bien servir la désactivation de celle-ci, j’imagine que c’est pour diminuer la consommation de la carte lorsqu’on n’utilise plus le bus SPI)
- MSTR = 1 : je veux fonctionner en mode master
- CPOL, CPHA et TAGD correspondent à différents modes de transmission (voir figure 22.2 du datasheet)

Un dernier point intéressant, la formule qui permet de calculer la valeur à mettre dans SPPREx.
Valeur = (PCLK / 2 / BaudRate) - 1 avec PCLK = 50Mhz sur la mini2440.
Exemple : baud rate voulu : 1Mhz, valeur = 24

Driver de type stream

Windows CE supporte plusieurs types de drivers. Le type « stream » est sans doute le plus simple à développer/utiliser dans des applications, c’est ce type que j’ai choisi.

Depuis une application, on accède à un driver de type « stream » avec les fonctions CreateFile, ReadFile, WriteFile, DeviceIoControl, CloseHandle. La fonction CreateFile ouvre le driver et retourne un handle qui permet ensuite d’accéder aux fonctions exportées par le driver. Dans mon cas, je ne vais pas supporter les fonctions ReadFile/WriteFile, mais passer par DeviceIoControl. En effet, un transfert SPI est en même temps une lecture et une écriture, et un transfert nécessite des paramètres supplémentaires (numéro de pin utilisés pour le signal CS par exemple).

Allez, il est temps de coder le driver.

Je commence par créer le répertoire C :\WINCE600\PLATFORM\Mini2440\SRC\DRIVERS\SPI qui va contenir les fichiers sources de mon driver. Pour que Platform Builder, lors de la génération de l’image, compile mon driver, je dois rajouter le nom de mon répertoire dans le fichier C :\WINCE600\PLATFORM\Mini2440\SRC\DRIVERS\dirs (ce fichier contient la liste des sous-répertoires explorés par le processus de génération).

J’ai choisi de coder mon driver dans 2 fichiers .cpp :
- drv.cpp : contient les fonctions d’interface d’un driver « stream », ce sont ces fonctions qui seront directement appelée par le système lors de l’accès au driver par une application.
- spi.cpp : le code proprement dit de mon driver.

Interface d’un driver « stream »

Un driver « stream » doit exporter les fonctions suivantes :
- DllEntry : appelée lors du chargement et du déchargement de notre driver.
- SPI_Init : appelée par le device manager pour initialiser notre driver.
- SPI_Open : appelée lorsqu’une application effectue un CreateFile de notre driver.
- SPI_Close : appelée lorsqu’une application effectue un CloseHandle de notre driver.
- SPI_Write : appelée lorsqu’une application effectue un WriteFile de notre driver.
- SPI_Read : appelée lorsqu’une application effectue un ReadFile de notre driver.
- SPI_IOControl : appelée lorsqu’une application effectue un DeviceIoControl de notre driver.

Accéder aux registres du processeur

Il n’est pas possible d’accéder directement aux registres du processeur en déclarant un pointeur à l’adresse du registre voulu. Il est nécessaire de passer par la mémoire virtuelle. Grosso modo, on déclare une zone mémoire virtuelle, une zone mémoire physique (à l’adresse du bloc de registre auxquels on veut accéder), et on mappe la zone de mémoire virtuelle sur la zone de mémoire physique. Les accès à la mémoire virtuelles iront alors directement dans la mémoire physique.

C’est le rôle de la fonction InternalMapRegisters. J’utilise ici 3 zones de mémoire, correspondant chacune à un bloc de registres du processeur.

Le code du driver

Pour être honnête, plutôt que réinventer la roue, je suis parti du code du driver i2c que j’ai adapté au bus SPI. Je ne vais pas détailler ici la totalité du code, j’ai essayé de le commenter le plus possible pour qu’il soit lisible tel quel.
J’ai choisi de stocker les paramètres de transmission dans la base de registres. C’est très bien dans mon cas, mais ce choix peut poser problème à ceux qui voudraient communiquer avec des composants SPI à des vitesses ou des formats de transmission différents. Dans ce cas, il n’est pas très difficile d’adapter le code en ajoutant une commande IOCTL permettant de régler ces paramètres.
Le driver peut à priori fonctionner sous interruption ou en polling. Je n’ai pas testé le mode polling qui ne présente à mon avis aucun intérêt.
Un mot quand-même sur l’interruption. Quand une interruption se déclenche, elle est récupérée par le système. Celui-ci regarde si une fonction a été enregistrée pour l’interruption en question et l’appelle si celle-ci existe. Pour déclarer une fonction d’interruption dans le système, il est nécessaire de connaître son numéro. On peut le trouver dans le fichier C :\WINCE600\PLATFORM\Mini2440\SRC\INC\s3c2440a_intr.h.
Enfin, le driver ne gère que l’interface SPI 0.

Intégration du driver dans notre image

Vous pouvez récupérer le code de mon driver ici.
Pour l’ajouter à votre image, il faut copier le répertoire spi dans C :\WINCE600\PLATFORM\Mini2440\SRC\DRIVERS\.
Il faut ensuite copier le fichier spi.h dans le répertoire C :\WINCE600\PLATFORM\Mini2440\SRC\INC.
Pour que le fichier binaire de notre driver (spi.dll) soit ajouté dans notre image, il faut rajouter la ligne suivante dans le fichier platform.bib dans la section MODULES (cette section commence à MODULES et se termine à FILES dans le fichier, entre les lignes 13 et 205) :

spi.dll $(_FLATRELEASEDIR)\spi.dll NK SHK


Enfin, il faut ajouter les clés suivantes dans la base de registre :

[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\SPI]
   "Prefix"="SPI"
   "Dll"="SPI.DLL"
   "Order"=dword:200
   "Index"=dword:0
   "Mode"=dword:1                    ; 0 = POLLING, 1 = INTERRUPT
   "ClockPolarity"=dword:1           ; 0 = ACTIVE_LOW, 1 = ACTIVE_HIGH
   "ClockPhase"=dword:1              ; 0 = FORMAT_A, 1 = FORMAT_B
   "TxAutoGarbage"=dword:1           ; 0 = NORMAL_MODE, 1 = TX_AUTO_GARBAGE
   "PrescalerValue"=dword:255               
   "FriendlyName"="SPI Bus Driver"

C’est grâce à ces clés que le système, lorsqu’une application effectue un CreateFile("SPI0 :", etc...), sait qu’il faut appeler la fonction SPI_Open() du fichier spi.dll.

Après génération de l’image, on doit normalement avoir le fichier spi.dll présent dans l’image.

Utilisation du driver SPI

Pour valider mon driver SPI, j’ai connecté une sonde de température SPI tmp125. Pourquoi une sonde de température ? Parce que j’en ai trouvé une qui traînait dans un tiroir, mais surtout parce qu’avec un tel composant, on connaît la valeur qu’on attend en lecture (la température de la pièce) et il suffit de souffler dessus pour la faire varier.

Au niveau électronique, le schéma est ultra simple :
- pin SI (2) du tmp125 sur la pin SPIMOSI du s3c2440 (broche 26, connecteur CON4)
- pin SO (4) du tmp125 sur la pin SPIMISO du s3c2440 (broche 25, connecteur CON4)
- pin SCK (6) du tmp125 sur la pin SPICLK du s3c2440 (broche 27, connecteur CON4)
- pin /CS (5) du tmp125 sur la pin de son choix du s3c2440 (j’ai choisi la pin 1 du port F = broche 10, connecteur CO4)
- pin V+ (3) et GND (1) du tmp125 sur broches VDD33V (2) et GND (3) du connecteur CON4.

Au niveau logiciel, ce n’est pas très compliqué. Vous pouvez télécharger les sources de mon application de test ici.

L’appel suivant permet de récupérer un handle sur notre driver :

hSPI = CreateFile(L"SPI0 :", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0) ;

Un transfert SPI s’effectue via l’appel suivant :

DeviceIoControl(hSPI, IOCTL_SPI_READ, &spiDesc, sizeof(SPI_IO_DESC), NULL, 0, &rCount, NULL) ;

Cet appel prend en paramètre une structure de type SPI_IO_DESC qui contient les paramètres suivants :
- portNumber : numéro du port de la pin /CS du composant (PORT_F dans mon cas)
- pinNumber : numéro de la pin /CS du composant (1 dans mon cas)
- Data : pointeur sur un buffer contenant les données à émettre/recevoir
- Count : taille du buffer en octets.

Pour les fans du compact framework, voici la même application en VB.NET.

Voilà, c’est la fin de cet article, j’espère ne pas avoir été trop brouillon, je n’ai pas voulu trop rentrer dans les détails (cet article n’est pas un tutoriel exhaustif sur la création d’un driver) en donnant quand même suffisamment d’infos à ceux qui voudraient comprendre comment le driver fonctionne.

Lire cet article en anglais

12 Messages de forum

  • A SPI driver for mini2440 26 avril 2010 09:12, par Darren

    Hi,

    I don’t suppose you’ve also done a GPIO driver for WinCE 6.0, by chance ???

    Thanks for your time in putting this site together.

    Cheers, Darren.

    • A SPI driver for mini2440 26 avril 2010 11:44, par Dom

      I just wrote such a driver, I have to test it before to write a article on my site and share the source code. I hope to do it before the end of this week.

      • A SPI driver for mini2440 27 avril 2010 01:54, par darren

        Legendary ! I look forward to the article.

        • A SPI driver for mini2440 29 avril 2010 06:15, par Martin

          I also look forward to the article.

          Is it possible to post a sample program using the SPI or I2C driver in VB.net ?

          Thanks Martin

          • A SPI driver for mini2440 30 avril 2010 14:26, par Dom

            The article is written, I only have to clean the source code before to publish the article and the source code. But the sample application will be in C++.

          • A SPI driver for mini2440 20 mai 2010 04:34, par bktan81

            Hi Martin,

            We have a modified version of dom’s spi driver, it works with our C# application.

            btw thank you dom for making this possible, we were struggling to get the spi driver for mini2440

            • A SPI driver for mini2440 20 mai 2010 09:38, par Dom

              I’m glad it helped you. What are the modifications you made ? I would be interested by seeing your c# source code if it is possible...

  • Un driver SPI pour la mini2440 30 avril 2010 11:21, par dlewin

    Salut,

    ton article est extra. j’ai le même besoin que toi....mais sous Linux. même s’il y à un peu plus dans les drivers : SCA3000 et lis3l02dq. Le truc c’est qu’en direct sans l’OS tu prend les registre tu suis les timings des specs, ça va. Avec l’OS c’est vraiment moins évident !!

    Utilises tu uniquement le SPI sous WinCE ou as tu fait la même approche sous Linux ?

    • Un driver SPI pour la mini2440 30 avril 2010 14:23, par Dom

      Non, désolé, je n’ai pas (encore ?) regardé ce qu’on pouvait faire avec la mini2440 sous linux. Il faut dire que je connais bien Windows CE car je le pratique au niveau professionnel. D’autre part, Windows CE est un vrai OS temps réel, caractéristique dont j’ai besoin pour le projet sur lequel je travaille à titre personnel (robotique).

      Lorsque j’ai écrit mon driver SPI, j’ai failli partir de zéro mais j’ai finalement trouvé plus rapide de cloner le driver I2C et l’adapter ensuite pour le bus SPI. C’est peut-être une bonne façon de faire également sous linux ?

  • A SPI driver for mini2440 12 mai 2010 11:50, par Grant Taylor

    Excellent work on all these articles.....it has been very interesting.

    Do you have any suggestions on how to interface to a Dallas DS18B20 device (http://datasheets.maxim-ic.com/en/d...). I’m not sure whether it is a true SPI device or not....I’m thinking not. I might have to use RS232 or something else.

    • A SPI driver for mini2440 12 mai 2010 20:41, par Dom

      Ths DS18B20 is a 1wire temperature sensor. 1wire is not a SPI or I2c bus, it’s another protocol. I can suggest you to use the 1wire to i2c bridge DS2482.

      This solution is in my to do list, for one day, when i have time...

      • A SPI driver for mini2440 13 mai 2010 01:24, par Grant Taylor

        Thanks for pointing me in the right direction. I will investigate the DS2482 I2C approach.

SPIP | Contact me | | Plan du site | Suivre la vie du site RSS 2.0      Version Française | English version