Accéder au contenu principal

Extension de ports GPIO à base de MCP 23017

Introduction

Le nombre de ports GPIO (ports d'entrée/sortie) sur les micro-contrôleurs et le Raspberry Pi peut se révéler limité pour certains projets. Il est généralement de 14 ports digitaux et de 8 ports analogiques pour les micro-contrôleurs de type Arduino et de 25 ports digitaux sur le Raspberry Pi.
Cela suffit pour faire clignoter quelques leds, ou lire quelques entrées digitales, mais peut être très limitant dès lors que l'on souhaite lire 32, 64, 96 ou 128 valeurs. 
Le Fez Panda II propose 40 ports supplémentaires qui sont généralement utilisés pour l'écran tactile Fez Touch et le Fez Cerbuino Bee propose 3 ports supplémentaires au format Gadgeteer.
Des solutions existent pour éviter d'utiliser trop de ports. Ces solutions utilisent généralement les protocoles séries SPI ou I2C, ce qui permet de ne monopoliser que 3 ports pour le protocole série (MISO, MOSI, SCLK) ou 2 ports avec le protocole I2C (SDA pour les données et SDC pour l'horloge).
Dans le cadre de la construction d'un tapis interactif de 10x10 cases, j'ai dû chercher une solution pour pouvoir lire 100 valeurs en utilisant un micro-contrôleur. J'ai utilisé pour cela un circuit intégré très peu coûteux: le MCP 23017 de MicroChip qui utilise le protocole I2C.

Description du protocole I2C

I2C utilise un bus de communication de données proposé par Philips en 1982 (voir une explication en français sur http://fr.wikipedia.org/wiki/I2C). Il utilise un protocole ni-directionnel en lecture/écriture asynchrones, ce qui permet d'envoyer des données sur le bus et éventuellement d'attendre une réponse du périphérique. C'est le protocole utilisé pour le driver Oled décrit dans un billet précédent. Dans ce cas, il est utilisé en écriture seule. Il est également utilisé pour lire des valeurs seules sur des puces IMU 6DOF (accelérateur et gyroscope, voir https://www.sparkfun.com/products/10121) par exemple. Ce protocole est notamment utilisé pour se connecter avec un Wii Nunchuck.
Dans mon travail de tous les jours, ce protocole est utilisé pour lire la vitesse de rotation d'un ventilateur d'alimentation ainsi que sa température.
Le circuit MCP 23017 qui peut ainsi être utilisé pour lire une valeur digitale ou en écrire une.
Le principe du bus i2c est le suivant. Le signal d'horloge donne les fronts montants et les fronts descendants. Dans l'exemple suivant, on voit que le SDA reste à 0 sur les ticks d'horloge pour représenter un bit à 0 et passe à 1 pour représenter un changement de bit à 1.

Plusieurs valeurs d'horloges sont possibles : 100 Kbits/s, 400 Kbits/s, 1Mbits/s.
Un bus i2c permet de s'adresser à plusieurs équipements, chacun ayant une adresse unique. Le message envoyé sur le bus contient donc les informations suivantes : adresse, commande, données.

Exemple pour l'affichage Oled 128x64
- adresse par défaut : 0x3C
- commande
  • 0x80 indique que l'on passe en mode commande
  • 0x40 indique que l'on passe en mode data
- données  :
  • 0xAE : affichage OFF
  • 0xAF : affichage ON
  • 0xA7 : affichage INVERSE
  • ...

Les données communiquées étant assez différentes d'un équipement à un autre, il est préférable de faire référence à la documentation du constructeur pour avoir le détail des commandes qui peuvent être exécutées.

Le circuit MCP 23017

Ce circuit de chez MicroChip permet d'adresser jusqu'à 16 ports GPIO digitaux. Il existe également un modèle MCP 23008 permettant d'adresser 8 ports GPIO. J'ai acheté le mien chez http://grobotronics.com pour 2€ car je n'en ai pas trouvé en France.




La représentation schématique est la suivante :
Notez bien l'orientation de pins qui est signalée par la petite encoche en haut de l'image. Pour ma part, lors de la mise en place sur le breadboard, j'ai préféré orienter les pins GPIO vers l'intérieur, ce qui nécessite de faire très attention dans les branchements et également d'avoir une numérotation inversée. On dispose cependant de plus d'espace pour faire les branchements de fils sans avoir à croiser les fils d'alimentation ou de données ou d'adressage qui eux sont généralement permanents.


Attention!!! Le MCP 23017 ne permet de piloter que des entrées/sorties digitales, donc du 1 ou du 0. Il n'est pas possible de lire directement des valeurs analogiques. Il existe cependant un autre circuit de chez MicroChip, le MCP 3008 (3€72 chez Lextronics), qui permet de lire des valeurs analogiques via le bus SPI : http://www.lextronic.fr/P30197-circuit-integre-mcp3008.html. Je décrirai ce modèle plus tard dès qu'il sera en ma possession.

J'ai pris le modèle DIP qui permet de s'enficher assez simplement dans un breadboard. Ce circuit se suffit pratiquement à lui seul. J'ai tout de même rajouté 2 résistances de 330 Ohms pour les fils de connexion SDA et SCL comme recommandé dans la spécification I2C. Pour la lecture, j'ai également rajouté des résistances connectées à GND pour une lecture de 0 par défaut. Je vais donc utiliser dans ce cas 8 ports pour la lecture et 8 ports pour l'écriture.


Ce schéma a été réalisé en ligne à l'adresse suivante : http://www.robotgear.com.au/Hosted/PEBBLE/Current/PEBBLE/PEBBLE%20for%20FF.html.
Petite astuce également, sur le circuit réel, j'ai remplacé les 8 résistances du dessous par un réseau de résistance qui s'enfiche directement sur le breadboard. Cela évite d'avoir à plier les résistances et simplifie grandement le câblage. J'ai trouvé les miens chez Lextronic pour 0,25€ le réseau de 9 résistances. L'une des pattes est utilisé comme point d'entrée.
Pour le tapis interactif en 10x10, j'ai utilisé un montage similaire mais avec un circuit complet pour l'écriture et un autre circuit pour la lecture. Pour un tapis de 8x8, un seul circuit aurait suffit.

Le MCP 23017 a essayé de maintenir une compatibilité avec le modèle 8 ports MCP 23008. Il offre donc 2 modes d'adressage des port désigné par Bank1 et Bank0. Vous remarquerez également que les commandes sont presques toutes dupliquées et se terminent par un A ou un B. Celles qui se terminent par A permettent d'adresser les 8 premiers ports et celles par B, les 8 suivants. Dans le mode Bank0 (par défaut), les ports A et B sont consécutifs. Dans le mode Bank1, les ports A se suivent de 0x00 à 0x0A et les ports B se suivent de 0x10 à 0x1A.
Les commandes utilisées sont décrites dans le tableau ci-dessous :


Programmation en C# pour le Fez Panda II

La programmation en C# pour le Fez Panda II est facilitée par l'existence d'une librairie additionnelle netmftoolbox. Bien qu'elle soit très orientée pour la carte Netduino. Elle peut être utilisée presque telle que pour les cartes GHI. J'ai cependant un peu souffert pour faire marcher le bus I2C natif sur les ports par défaut AN5 et AN6. J'ai donc décidé de passer par le mode I2C logiciel (SoftI2C) un peu plus lent, mais qui a marché du premier coup.
Commencez par installer les librairies additionnelles. Vous pouvez également télécharger le code source pour le modifier et simplement vous en inspirer.

Voici le code source permettant l'utilisation du Software I2C.

 using System;  
 using Microsoft.SPOT;  
 /*  
  * Copyright 2012-2014 Stefan Thoolen (http://www.netmftoolbox.com/)  
  *   
  * This code is inspired on the Adafruit MCP23017 library for Arduino  
  * available at https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library  
  *  
  * Licensed under the Apache License, Version 2.0 (the "License");  
  * you may not use this file except in compliance with the License.  
  * You may obtain a copy of the License at  
  *  
  *   http://www.apache.org/licenses/LICENSE-2.0  
  *  
  * Unless required by applicable law or agreed to in writing, software  
  * distributed under the License is distributed on an "AS IS" BASIS,  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  * See the License for the specific language governing permissions and  
  * limitations under the License.  
  */  
 namespace Toolbox.NETMF.Hardware  
 {  
   /// <summary>  
   /// MCP23017 pin expander  
   /// </summary>  
   public class SoftMcp23017  
   {  
     #region "Contructor"  
     /// <summary>  
     /// Reference to the I²C bus  
     /// </summary>  
     private SoftMultiI2C _Device;  
     // Registers for port A  
     private const byte _IODIRA = 0x00;  
     private const byte _GPPUA = 0x0c;  
     private const byte _GPIOA = 0x12;  
     private const byte _OLATA = 0x14;  
     // Registers for port B  
     private const byte _IODIRB = 0x01;  
     private const byte _GPPUB = 0x0d;  
     private const byte _GPIOB = 0x13;  
     private const byte _OLATB = 0x15;  
     /// <summary>  
     /// Initialises a new MCP23017 pin expander  
     /// </summary>  
     /// <param name="Address">The I²C address</param>  
     /// <param name="ClockRateKhz">The module speed in Khz</param>  
     public SoftMcp23017(ushort Address = 0x20, int ClockRateKhz = 200)  
     {  
       // Initialises the device  
       this._Device = new SoftMultiI2C(Address, ClockRateKhz);  
       // Set all pins to input by default  
       this._Device.Write(new byte[] { _IODIRA, 0xff }); // All pins on port A  
       this._Device.Write(new byte[] { _IODIRB, 0xff }); // All pins on port B  
       // Prepares all pins  
       this.Pins = new SoftMcp23017Port[16];  
       for (int PinNo = 0; PinNo < this.Pins.Length; ++PinNo)  
         this.Pins[PinNo] = new SoftMcp23017Port(this, PinNo);  
     }  
     #endregion  
     #region "Reading/writing"  
     /// <summary>  
     /// Reads the state of a pin  
     /// </summary>  
     /// <param name="Pin">The pin (0 to 15)</param>  
     /// <returns>True if it's high, false if it's low</returns>  
     protected bool GetPin(int Pin)  
     {  
       // Pin in range?  
       if (Pin < 0 || Pin > 15) throw new IndexOutOfRangeException("There are only 16 pins");  
       // By default we work on block A  
       byte GpioAddress = _GPIOA;  
       // Pins 8 and higher are on block B  
       if (Pin > 7)  
       {  
         Pin -= 8;  
         GpioAddress = _GPIOB;  
       }  
       // Read the current GPIO values  
       byte[] ReadBuffer = new byte[1];  
       this._Device.WriteRead(new byte[] { GpioAddress }, ReadBuffer);  
       byte GpioValues = ReadBuffer[0];  
       // Returns the value of this specific pin  
       return ((GpioValues >> Pin) & 0x01) == 0x01;  
     }  
     /// <summary>  
     /// Enables pull-ups  
     /// </summary>  
     /// <param name="Pin">The pin (0 to 15)</param>  
     /// <param name="PullHigh">True if the pin must be pulled high, false if it must not be</param>  
     public void EnablePullup(int Pin, bool PullHigh)  
     {  
       // Pin in range?  
       if (Pin < 0 || Pin > 15) throw new IndexOutOfRangeException("There are only 16 pins");  
       // By default we work on block A  
       byte PullAddress = _GPPUA;  
       // Pins 8 and higher are on block B  
       if (Pin > 7)  
       {  
         Pin -= 8;  
         PullAddress = _GPPUB;  
       }  
       // Read the current pull-up values  
       byte[] ReadBuffer = new byte[1];  
       this._Device.WriteRead(new byte[] { PullAddress }, ReadBuffer);  
       byte PullValues = ReadBuffer[0];  
       // Toggles the pullup value  
       if (PullHigh)  
         PullValues |= (byte)(1 << Pin);  
       else  
         PullValues &= (byte)~(1 << Pin);  
       // Writes the new pull-up values  
       this._Device.Write(new byte[] { PullAddress, PullValues });  
     }  
     /// <summary>  
     /// Sets the state of an output port  
     /// </summary>  
     /// <param name="Pin">The pin (0 to 15)</param>  
     /// <param name="Value">True for high, false for low</param>  
     protected void SetPin(int Pin, bool Value)  
     {  
       // Pin in range?  
       if (Pin < 0 || Pin > 15) throw new IndexOutOfRangeException("There are only 16 pins");  
       // By default we work on block A  
       byte OlatAddress = _OLATA;  
       byte GpioAddress = _GPIOA;  
       // Pins 8 and higher are on block B  
       if (Pin > 7)  
       {  
         Pin -= 8;  
         OlatAddress = _OLATB;  
         GpioAddress = _GPIOB;  
       }  
       // Read the current GPIO values  
       byte[] ReadBuffer = new byte[1];  
       this._Device.WriteRead(new byte[] { OlatAddress }, ReadBuffer);  
       byte GpioValues = ReadBuffer[0];  
       // Toggles the pin value  
       if (Value)  
         GpioValues |= (byte)(1 << Pin);  
       else  
         GpioValues &= (byte)~(1 << Pin);  
       // Writes the new GPIO values  
       this._Device.Write(new byte[] { GpioAddress, GpioValues });  
     }  
     /// <summary>  
     /// Sets the state of multiple output ports  
     /// </summary>  
     /// <param name="StartBit">The first bit to write</param>  
     /// <param name="Data">The data to write</param>  
     /// <param name="BitCount">The amount of bits to write</param>  
     /// <param name="Inverted">When true, bits will be inverted</param>  
     protected void WriteByte(uint StartBit, uint Data, int BitCount, bool Inverted = false)  
     {  
       // Lets read all values  
       byte[] ReadBuffer = new byte[1];  
       this._Device.WriteRead(new byte[] { _OLATA }, ReadBuffer); byte BlockA = ReadBuffer[0];  
       this._Device.WriteRead(new byte[] { _OLATB }, ReadBuffer); byte BlockB = ReadBuffer[0];  
       // Changes all bits  
       for (int BitNo = 0; BitNo < BitCount; ++BitNo)  
       {  
         // Gets the new value of the specific bit  
         int BitMask = 1 << BitNo;  
         bool NewValue = (Data & BitMask) == BitMask;  
         // Gets the pin number  
         int PinNo;  
         if (Inverted)  
           PinNo = (int)(StartBit + BitNo);  
         else   
           PinNo = (int)(StartBit + BitCount - 1 - BitNo);  
         // Sets the value to the right bit block  
         if (PinNo < 8)  
         {  
           if (NewValue)  
             BlockA |= (byte)(1 << PinNo);  
           else  
             BlockA &= (byte)~(1 << PinNo);  
         }  
         else  
         {  
           PinNo -= 8;  
           if (NewValue)  
             BlockB |= (byte)(1 << PinNo);  
           else  
             BlockB &= (byte)~(1 << PinNo);  
         }  
       }  
       // Writes the new GPIO values  
       this._Device.Write(new byte[] { _GPIOA, BlockA });  
       this._Device.Write(new byte[] { _GPIOB, BlockB });  
     }  
     /// <summary>  
     /// Changes the mode of a pin  
     /// </summary>  
     /// <param name="Pin">The pin (0 to 15)</param>  
     /// <param name="Output">True for output port, false for input port</param>  
     protected void PinMode(int Pin, bool Output)  
     {  
       // Pin in range?  
       if (Pin < 0 || Pin > 15) throw new IndexOutOfRangeException("There are only 16 pins");  
       // By default we work on block A  
       byte IODirAddress = _IODIRA;  
       // Pins 8 and higher are on block B  
       if (Pin > 7)  
       {  
         Pin -= 8;  
         IODirAddress = _IODIRB;  
       }  
       // Requests the current block  
       byte[] ReadBuffer = new byte[1];  
       this._Device.WriteRead(new byte[] { IODirAddress }, ReadBuffer);  
       byte IODirections = ReadBuffer[0];  
       // Toggles the bit for the right pin  
       if (Output)  
         IODirections &= (byte)~(1 << Pin);  
       else  
         IODirections |= (byte)(1 << Pin);  
       // Writes the new value  
       this._Device.Write(new byte[] { IODirAddress, IODirections });  
     }  
     #endregion  
     #region "Tristate pins"  
     /// <summary>IRQ Port wrapper for the SPIShifterIn class</summary>  
     protected class SoftMcp23017Port : ITRIPort  
     {  
       /// <summary>Reference to the main chip</summary>  
       private SoftMcp23017 _Module;  
       /// <summary>The number of the pin</summary>  
       private int _PinNo;  
       /// <summary>True when this is an outputport</summary>  
       private bool _IsOutput = false;  
       /// <summary>  
       /// Defines a Tristate Port  
       /// </summary>  
       /// <param name="Module">The object of the main chip</param>  
       /// <param name="PinNo">The number of the pin</param>  
       public SoftMcp23017Port(SoftMcp23017 Module, int PinNo)  
       {  
         this._Module = Module;  
         this._PinNo = PinNo;  
       }  
       /// <summary>Writes the pin value</summary>  
       /// <param name="State">True for high, false for low</param>  
       public void Write(bool State)  
       {  
         if (!this._IsOutput)  
         {  
           this._IsOutput = true;  
           this._Module.PinMode(this._PinNo, true);  
         }  
         this._Module.SetPin(this._PinNo, State);  
         this.State = State;  
       }  
       /// <summary>Reads the pin value</summary>  
       /// <returns>True when high, false when low</returns>  
       public bool Read()  
       {  
         if (this._IsOutput)  
         {  
           this._IsOutput = false;  
           this._Module.PinMode(this._PinNo, false);  
         }  
         return this.InvertReadings ? !this._Module.GetPin(this._PinNo) : this._Module.GetPin(this._PinNo);  
       }  
       /// <summary>True when the pin is high, false when low</summary>  
       public bool State { get; protected set; }  
       /// <summary>Frees the pin</summary>  
       public void Dispose() { }  
       /// <summary>When true, the read value is inverted (useful when working with pull-up resistors)</summary>  
       public bool InvertReadings { get; set; }  
     }  
     /// <summary>Reference to all pins</summary>  
     public ITRIPort[] Pins;  
     #endregion  
     #region "Parallel Out support"  
     /// <summary>  
     /// Parallel Out class  
     /// </summary>  
     protected class SoftMcp23017ParallelOut : IParallelOut  
     {  
       /// <summary>Reference to the main chain</summary>  
       private SoftMcp23017 _Module;  
       /// <summary>The bit to start at</summary>  
       private uint _StartBit;  
       /// <summary>The amount of bits in this chain</summary>  
       private uint _BitCount;  
       /// <summary>The buffer of the data</summary>  
       private uint _Buffer = 0;  
       /// <summary>When true, bits will be inverted</summary>  
       private bool _Inverted;  
       /// <summary>Frees the pin for other usage</summary>  
       public void Dispose() { }  
       /// <summary>Initialises a new parallel output port</summary>  
       /// <param name="Module">The object of the main chain</param>  
       /// <param name="StartBit">The first bit to write</param>  
       /// <param name="BitCount">The amount of bits to write</param>  
       /// <param name="Inverted">When true, bits will be inverted</param>  
       public SoftMcp23017ParallelOut(SoftMcp23017 Module, uint StartBit, uint BitCount, bool Inverted)  
       {  
         this._Module = Module;  
         this._StartBit = StartBit;  
         this._BitCount = BitCount;  
         this._Inverted = Inverted;  
       }  
       /// <summary>Returns the last written block of data</summary>  
       /// <returns>The last written block of data</returns>  
       public uint Read()  
       {  
         return this._Buffer;  
       }  
       /// <summary>Amount of bits in the array</summary>  
       public uint Size { get { return this._BitCount; } }  
       /// <summary>Writes a block of data to the array</summary>  
       /// <param name="Value">The block of data to write</param>  
       public void Write(uint Value)  
       {  
         this._Buffer = Value;  
         this._Module.WriteByte(this._StartBit, this._Buffer, (int)this._BitCount, this._Inverted);  
       }  
     }  
     /// <summary>  
     /// Creates a new parallel output port on this IC chain  
     /// </summary>  
     /// <param name="StartBit">The first bit to write to</param>  
     /// <param name="BitCount">The amount of bits</param>  
     /// <param name="Inverted">When true, bits will be inverted</param>  
     /// <returns>Parallel output port object</returns>  
     public IParallelOut CreateParallelOut(uint StartBit = 0, uint BitCount = 8, bool Inverted = false)  
     {  
       // Sets all pins as outputs  
       for (uint PinNo = StartBit; PinNo < (StartBit + BitCount); ++PinNo)  
         this.PinMode((int)PinNo, true);  
       // Returns the new port  
       return new SoftMcp23017ParallelOut(this, StartBit, BitCount, Inverted);  
     }  
     #endregion  
   }  
 }  

Ce code s'appuie sur la classe SoftwareMultiI2C suivante.

 using System;  
 using Microsoft.SPOT.Hardware;  
 using GHIElectronics.NETMF.Hardware;  
 using GHIElectronics.NETMF.FEZ;  
 using Microsoft.SPOT;  
 /*  
  * Copyright 2012-2014 Stefan Thoolen (http://www.netmftoolbox.com/)  
  *  
  * Licensed under the Apache License, Version 2.0 (the "License");  
  * you may not use this file except in compliance with the License.  
  * You may obtain a copy of the License at  
  *  
  *   http://www.apache.org/licenses/LICENSE-2.0  
  *  
  * Unless required by applicable law or agreed to in writing, software  
  * distributed under the License is distributed on an "AS IS" BASIS,  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  * See the License for the specific language governing permissions and  
  * limitations under the License.  
  */  
 namespace Toolbox.NETMF.Hardware  
 {  
   /// <summary>  
   /// I²C Helper to make it easier to use multiple I²C-devices on one I²C-bus  
   /// </summary>  
   public class SoftMultiI2C  
   {  
     static private SoftwareI2CBus _i2cBus = null;  
     private SoftwareI2CBus.I2CDevice _I2CDevice;  
     /// <summary>I²C Configuration. Different for each device, so not a static reference</summary>  
     private I2CDevice.Configuration _Configuration;  
     /// <summary>Transaction timeout</summary>  
     public int Timeout { get; set; }  
     /// <summary>The address of the I²C device</summary>  
     public ushort DeviceAddress { get { return this._Configuration.Address; } }  
     /// <summary>The speed of the I²C device</summary>  
     public int ClockRateKhz { get { return this._Configuration.ClockRateKhz; } }  
     /// <summary>  
     /// Initializes a new I²C device  
     /// </summary>  
     /// <param name="Address">The address of the I²C device</param>  
     /// <param name="ClockRateKhz">The speed in Khz of the I²C device</param>  
     public SoftMultiI2C(ushort Address, int ClockRateKhz = 100)  
     {  
       // Sets the configuration in a local value  
       this._Configuration = new I2CDevice.Configuration(Address, ClockRateKhz);  
       // Sets the default timeout  
       this.Timeout = 100;  
       // If no I2C Device exists yet, we create it's first instance  
       if (_I2CDevice == null)  
       {  
         if (_i2cBus == null)  
           _i2cBus = new SoftwareI2CBus((Cpu.Pin)FEZ_Pin.Digital.An5, (Cpu.Pin)FEZ_Pin.Digital.An4);  
         //this.i2c = new I2CDevice(new I2CDevice.Configuration(address, 100));  
         this._I2CDevice = _i2cBus.CreateI2CDevice((ushort)Address, ClockRateKhz);  
         // Creates the SPI Device  
         //_I2CDevice = new I2CDevice(this._Configuration);  
       }  
     }  
     public void Write(byte reg, byte val)  
     {  
       WriteRegister(reg, val);  
     }  
     /// <summary>  
     /// Read single register  
     /// </summary>  
     /// <param name="reg">Register name</param>  
     /// <returns>Register value</returns>  
     public byte Read(byte reg)  
     {  
       return ReadRegister(reg, 1)[0];  
     }  
     private void WriteRegister(byte reg, byte value)  
     {  
       byte[] RegisterNum = new byte[2] { reg, value };  
       //i2c.Write(RegisterNum, 1000);  
       _I2CDevice.Write(RegisterNum, 0, RegisterNum.Length);  
     }  
     private byte[] ReadRegister(byte reg, int readcount)  
     {  
       int numWrite;  
       int numRead;  
       byte[] RegisterNum = new byte[1] { reg };  
       // create read buffer to read the register  
       byte[] RegisterValue = new byte[readcount];  
       //i2c.WriteRead(RegisterNum, RegisterValue, 1000);  
       bool result = _I2CDevice.WriteRead(RegisterNum, 0, RegisterNum.Length, RegisterValue, 0, RegisterValue.Length, out numWrite, out numRead);  
       if (result)  
       {  
         Debug.Print(" - result is: " + RegisterValue[0].ToString());  
       }  
       return RegisterValue;  
     }  
     /// <summary>  
     /// The 8-bit bytes to write to the I²C-buffer  
     /// </summary>  
     /// <param name="WriteBuffer">An array of 8-bit bytes</param>  
     /// <returns>The amount of transferred bytes</returns>  
     public int Write(byte[] WriteBuffer)  
     {  
       return _I2CDevice.Write(WriteBuffer, 0, WriteBuffer.Length);  
     }  
     /// <summary>  
     /// The 16-bit bytes to write to the I²C-buffer  
     /// </summary>  
     /// <param name="WriteBuffer">An array of 16-bit bytes</param>  
     /// <returns>The amount of transferred bytes</returns>  
     public int Write(ushort[] WriteBuffer)  
     {  
       // Transforms the write buffer to 8-bit bytes and returns the value devided by 2 (to get the amount of 16-bit bytes again)  
       return this.Write(Tools.UShortsToBytes(WriteBuffer)) / 2;  
     }  
     ///// <summary>  
     ///// Reads 8-bit bytes  
     ///// </summary>  
     ///// <param name="ReadBuffer">An array with 8-bit bytes to read</param>  
     ///// <returns>The amount of transferred bytes</returns>  
     public int Read(byte[] ReadBuffer)  
     {  
       return _I2CDevice.Read(ReadBuffer, 0, ReadBuffer.Length);  
     }  
     /// <summary>  
     /// Reads 16-bit bytes  
     /// </summary>  
     /// <param name="ReadBuffer">An array with 16-bit bytes to read</param>  
     /// <returns>The amount of transferred bytes</returns>  
     public int Read(ushort[] ReadBuffer)  
     {  
       // Creates an 8-bit readbuffer  
       byte[] bReadBuffer = new byte[ReadBuffer.Length * 2];  
       // Actually executes the transaction  
       int Transferred = this.Read(bReadBuffer);  
       // Converts the 8-bit readbuffer to 16-bit  
       ReadBuffer = Tools.BytesToUShorts(bReadBuffer);  
       // Devide by 2 to get the amount of 16-bit bytes  
       return Transferred / 2;  
     }  
     /// <summary>  
     /// Writes an array of 8-bit bytes to the interface, and reads an array of 8-bit bytes from the interface.  
     /// </summary>  
     /// <param name="WriteBuffer">An array with 8-bit bytes to write</param>  
     /// <param name="ReadBuffer">An array with 8-bit bytes to read</param>  
     /// <returns>The amount of transferred bytes</returns>  
     public int WriteRead(byte[] WriteBuffer, byte[] ReadBuffer)  
     {  
       this.Write(WriteBuffer);  
       return this.Read(ReadBuffer);  
     }  
     /// <summary>  
     /// Writes an array of 16-bit bytes to the interface, and reads an array of 16-bit bytes from the interface.  
     /// </summary>  
     /// <param name="WriteBuffer">An array with 16-bit bytes to write</param>  
     /// <param name="ReadBuffer">An array with 16-bit bytes to read</param>  
     /// <returns>The amount of transferred bytes</returns>  
     public int WriteRead(ushort[] WriteBuffer, ushort[] ReadBuffer)  
     {  
       // Creates an 8-bit readbuffer  
       byte[] bReadBuffer = new byte[ReadBuffer.Length * 2];  
       // Actually executes the transaction  
       int Transferred = this.WriteRead(Tools.UShortsToBytes(WriteBuffer), bReadBuffer);  
       // Converts the 8-bit readbuffer to 16-bit  
       ReadBuffer = Tools.BytesToUShorts(bReadBuffer);  
       // Devide by 2 to get the amount of 16-bit bytes  
       return Transferred / 2;  
     }  
   }  
 }  

Et un exemple d'utilisation.

 using System;  
 using System.Threading;  
 using System.IO.Ports;  
 using Microsoft.SPOT;  
 using Microsoft.SPOT.Hardware;  
 using GHIElectronics.NETMF.FEZ;  
 using GHIElectronics.NETMF.System;  
 using GHIElectronics.NETMF.IO;  
 using GHIElectronics.NETMF.Hardware;  
 using Toolbox.NETMF.Hardware;  
 using System.IO;  
 namespace Les100Dalles  
 {  
   public class Program  
   {  
     static SoftMcp23017 mux1;  
     public static void Chenillard()  
     {  
       while (true)  
       {  
         bool bState = true;  
         int last = -1;  
         for (int i = 0; i < 7; i++)  
         {  
           mux1.Pins[i].Write(true);  
           if (last > -1)  
           {  
             mux1.Pins[last].Write(false);  
           }  
           last = i;  
           Thread.Sleep(50);  
         }  
         for (int i = 7; i > -1; i--)  
         {  
           mux1.Pins[i].Write(true);  
           if (last > -1)  
           {  
             mux1.Pins[last].Write(false);  
           }  
           last = i;  
           Thread.Sleep(50);  
         }  
         led.Write(!bState);  
         bState = !bState;  
         Thread.Sleep(300);  
       }  
     }  
     public static void Main()  
     {  
       Chenillard();  
     }  
   }  
 }  


Vous trouverez également des informations complémentaires sur le site très bien documenté de Christian Loris.


Commentaires

Posts les plus consultés de ce blog

Utilisez votre tablette Android comme second écran pour Linux (Raspberry Pi, MK908II)

Les tablettes Android atteignent désormais des prix qui défient toute concurrence. On trouve désormais des modèles à 39 € TTC en super marché, soit à peine plus cher que le Raspberry PI, mais avec un écran. Ces modèles souvent mono-core 1Ghz ou 1,4 Ghz avec 512 ou 1Go de mémoire ne sont très probablement pas utilisables pour une utilisation régulière sur Internet et ne sont en aucun point comparables à leur équivalent de marque (Samsung, Sony, LG, HTC, Lenovo, etc). Plusieurs tutoriels indiquent comment connecter utiliser une tablette Android comme second écran ( http://www.linux-magazine.com/Online/Blogs/Productivity-Sauce/Use-an-Android-Device-as-Screen-and-Input-for-Raspberry-Pi ). Ces méthodes utilisent généralement l'USB Tethering qui n'est malheureusement disponible que sur les téléphones ou tablettes avec un accès mobile (3G ou 4G) inclus. Dans ce billet, je vais vous montrer comment se connecter à une tablette en utilisant le mode Debug adb (Android Debug Bridge

Supprimer les partitions Raspberry sur une carte SD sous Windows avec Diskpart

Si vous souhaitez récupérer une ancienne carte SD utilisée pour démarrer un Raspberry pour un autre usage (appareil photo, etc), il vous faudra supprimer les deux partitions créées au moment de l'écriture de l'image sur la carte SD. Vous pouvez voir les partition en sélectionnant Menu Windows/Ordinateur/bouton droit "Gérer". Voici un exemple du résultat final. Vous pouvez supprimer la partition Unix de 7 Gb (ou 4Gb en fonction de la taille de votre carte) en sélectionnant la partition puis en faisant "bouton droit Supprimer". Laissez juste une partition pour pouvoir faire les autres manipulations avec DISKPART. Démarrez l'outil DISKPART en ligne de commande. Une nouvelle fenêtre s'ouvrira. Microsoft DiskPart version 6.1.7601 Copyright (C) 1999-2008 Microsoft Corporation. DISKPART> list disk   N° disque  Statut         Taille   Libre    Dyn  GPT   ---------  -------------  -------  -------  ---  ---   Disque 0    En ligne        238 G octe

Emulateur Raspberry Pi sous Windows

Si vous souhaitez développer ou tester des applications pour Raspberry Pi, vous pouvez, soit compiler directement l'application sur Raspberry, soit la développer sous Linux ou Windows et la compiler pour Raspberry. La seconde solution est souvent plus simple car elle permet de disposer d'un environnement de compilation complet tel qu'Eclipse pour le développement. Une fois l'application développée, il faut la tester sur Raspberry. Là, il faut copier l'application en utilisant un client FTP ou SCP, puis se connecter en SSH et lancer l'exécutable. Il existe un autre moyen de tester une application Raspberry sans avoir à l'allumer. Il suffit de passer par un émulateur tel que QEMU qui permet de lancer un OS pour processeur ARM sous Linux ou Windows. L'émulateur sous Windows 1. Récupérez l'émulateur à l'adresse suivante : http://sourceforge.net/projects/rpi-emulator-win32/ . 2. Dézippés le contenu de l'image Rpi-Occidentalis-v02-qemu.7z av