Month: January 2014

.Net Micro Framework 4.3 QFE1 : news sullo stato del rilascio

E’ da Ottobre che seguo costantemente il post sul blog ufficiale del .Net Micro Framework per carpire le novità sul rilascio della versione 4.3 QFE1 (attualmente in RTM).

Pochi giorni fa c’è stato un aggiornamento di Philip Lo (che a quanto pare fa parte del team insieme all’autore del post ShiZhe Jiang) che parla di un “legal team” che sta bloccando il rilascio su CodePlex da Dicembre 2013 …. chissà perchè !

Di seguito le parole di Philip Lo …

For the .NET MF 4.3 QFE 1 release, I’m wrestling with our legal team to get this update approved for publishing on Codeplex since early December. Seems legal is shuffling people around, and new faces with no history of past .NET MF releases is causing hiccups. Needless to say I’m trying other avenues to jog legal’s memory of past releases to get this update approved.

Anche questa volta non ci resta che aspettare, sperando che non si tratti di un nuovo aggiornamento sul blog ma del rilascio ufficiale su CodePlex !

🙂

Channel9 : bus SPI ed il .Net Micro Framework su Coding4Fun

0763.channel9_thumb_6C8E715B

Citando le parole di Greg Duncan sono ormai un “friend of the blog” !

L’appuntamento settimanale del venerdì nel blog Coding4Fun su Channel9 fa riferimento al mio articolo sul bus SPI ed il suo utilizzo attraverso il .Net Micro Framework (su una board Netduino).

E’ un piacere esserci !

🙂

uNFC library : NFC con le piattaforme Windows Embedded

Dopo i due articoli precedenti che descrivevano il bus SPI ed I2C ed il loro utilizzo nel .Net Micro Framework, è giunto finalmente il momento di vederne un’applicazione pratica con il mio ultimo progetto disponibile su CodePlex : uNFC – NFC library for .Net platforms !

Introduzione : supporto software ed hardware

Questa libreria permette di utilizzare un chip NFC collegato al nostro PC (via seriale) oppure ad un qualsiasi sistema embedded che sia basato su Windows Embedded Compact / Windows Embedded Standard o .Net Micro Framework.

Essa supporta tutti le piattaforme .Net Framework disponibili :

  • .Net Framework 4.0 per PC con Windows 7/8 oppure sistemi embedded basati su Windows Embedded Standard 7 e Windows Embedded 8 Standard;
  • .Net Compact Framework 3.5 / 3.9 per sistemi embedded basati su Windows Embedded Compact 7 e Windows Embedded Compact 2013;
  • .Net Micro Framework 4.2 e 4.3 per sistemi embedded come le board Netduino e .Net Gadgeteer;

Per quanto riguarda l’hardware, essa attualmente supporta il chip NFC PN532 della NXP ma definisce un “piccolo framework” grazie al quale è possibile sviluppare un managed driver per un chip differente senza modificare tutti i livelli superiori che si interfacciano con l’applicazione utente. Il supporto al chip PN532 è garantito con tutti e tre i possibili canali di comunicazione previsti per esso : I2C, SPI ed HSU.

Tipicamente il canale HSU (High Speed UART) viene utilizzato per il collegamento alla porta seriale di un PC o di un sistema basato su Windows Embedded Compact. Le connessioni I2C ed SPI rappresentano le soluzioni migliori nel caso di board basate su .Net Micro Framework.

Lo sviluppo ed il testing è stato effettuato mediante la breakout board RFID/NFC della Elecfreaks e disponibile al seguente link, così come il datasheet ufficiale di riferimento del chip NXP PN532 è disponibile qui. Per il collegamento ad un PC è ovviamente necessario un convertitore USB – Seriale TTL (come questi della FTDI) con i relativi driver, in modo da interfacciarsi al lettore sempre attraverso una porta seriale (in questo caso virtuale).

8664.NFC-Module_thumb_16FC890B

Architettura software

Il managed driver per il chip PN532 è implementato dalla classe PN532 che ha un riferimento ad un’istanza del layer di comunicazione che deve implementare l’interfaccia IPN532CommunicationLayer. In questo modo, abbiamo la possibilità di scegliere il canale da utilizzare fornendo l’istanza di una classe concreta che implementa questa interfaccia al costruttore della classe PN532. Le classe disponibili sono le seguenti :

  • PN532CommunicationI2C : canale I2C;
  • PN532CommunicationSPI : canale SPI;
  • PN532CommunicationHSU : canale HSU;

Il lettore basato su questo chip è implementato attraverso la classe NfcPN532Reader che ha un riferimento all’istanza della PN532. Questo classe implementa l’interfaccia INfcReader. Ciò vuol dire che se vogliamo utilizzare un altro chip, dobbiamo seguire questi passi :

  • Realizzare una classe per il driver (come la PN532);
  • Realizzare una classe relativa al lettore che ha un riferimento al driver suddetto ed implementa l’interfaccia INfcReader;

L’esistenza di più classi nell’implementazione per il chip PN532 è strettamente legata al fatto che quest’ultimo disponga di più canali di comunicazione per l’interfacciamento.

5554.ClassDiagram1_thumb_1D435F99

In primo luogo va creata un’istanza di una delle classi relative al canale di comunicazione e ciascuna di esse è strettamente legata alla board sulla quale stiamo lavorando. Successivamente tale istanza va fornita al costruttore della classe PN532 e quest’ultima va passata al costruttore della classe del lettore NfcPN532Reader.

L’interfaccia INfcReader, che essa implementa, espone :

  • il metodo Open() per l’inizializzazione del lettore, specificando il tipo di tag NFC che ci aspettiamo di poter riconoscere;
  • il metodo Close() per chiudere la connessione al lettore;
  • il metodo WriteRead() per scrivere e leggere verso/da il lettore;
  • l’evento TadDetected che viene sollevato quando un tag è riconosciuto dal lettore;
  • l’evento TagLost che viene sollevato quando il lettore perder la connessione con il tag riconosciuto;

Utilizzando gli ultimi due eventi, è possibile registrare un event handler per ciascuno di essi in modo da poter gestire il riconoscimento di un tag da parte del lettore e la disconnessione del tag stesso.

5047.IMG_20140104_093307_thumb_3566C9F4

Di seguito, un esempio di applicazione per la board Netduino Plus sulla quale possiamo utilizzare tutti i canali di comunicazione disponibili nel PN532 ma è ovviamente possibile istanziare ed utilizzare un solo canale alla volta (nella foto in alto oltre al Netduino Plus ed alla board dell’NFC, c’è anche un analizzatore logico usato durante lo sviluppo).

static private INfcReader nfc;

public static void Main()
{
 // write your code here

 // HSU Communication layer
 // MOSI/SDA (TX) --> DIGITAL 0 (RX COM1)
 // SCL/RX (RX) --> DIGITAL 1 (TX COM1)
 IPN532CommunicationLayer commLayer = new PN532CommunicationHSU(SerialPorts.COM1);

 // SPI Communication layer
 // SCK --> DIGITAL 13
 // MISO --> DIGITAL 12
 // MOSI/SDA --> DIGITAL 11
 // SCL/RX -> DIGITAL 10
 // IRQ --> DIGITAL 8
 //IPN532CommunicationLayer commLayer = new PN532CommunicationSPI(SPI.SPI_module.SPI1, Pins.GPIO_PIN_D10, Pins.GPIO_PIN_D8);

 // I2C Communication layer
 // MOSI/SDA --> ANALOG 4 (SDA)
 // SCL/RS --> ANALOG 5 (SCL)
 // IRQ --> DIGITAL 8
 //IPN532CommunicationLayer commLayer = new PN532CommunicationI2C(Pins.GPIO_PIN_D8);

 nfc = new NfcPN532Reader(commLayer);
 nfc.TagDetected += nfc_TagDetected;
 nfc.TagLost += nfc_TagLost;
 nfc.Open(NfcTagType.MifareUltralight);

 InterruptPort button = new InterruptPort(Pins.ONBOARD_SW1, true, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeHigh);
 button.OnInterrupt += button_OnInterrupt;

 Thread.Sleep(Timeout.Infinite);
}

static void button_OnInterrupt(uint data1, uint data2, DateTime time)
{
 nfc.Close();
}

In entrambi gli eventi, l’istanza NfcTagEventArgs espone alcune informazioni sul tag riconosciuto (il tipo) ed un riferimento alla connessione al tag. Tale connessione è rappresentata da un’istanza di una classe che deriva dalla classe astratta NfcTagConnection. Attualmente sono disponibili le classi NfcMifareTagConnection e NfcMifareUITagConnection per la gestione rispettivamente del tag Mifare Classic e Mifare Ultralight.

La classe astratta ha a disposizione :

  • la property ID, ossia l’NFC ID del tag;
  • un riferimento all’interfaccia INfcReader che punterà all’istanza NfcPN532Reader utilizzata per attivare il lettore. Attraverso questa istanza, l’oggetto relativo alla connection è in grado di utilizzare il metodo WriteRead() visto prima per scrivere e leggere verso/da il lettore;

0576.ClassDiagram2_thumb_6DA5410C

Tornando all’esempio precedente, attraverso i due event handler possiamo mostrare l’ID del tag riconosciuto e magari effettuare delle operazioni di lettura scrittura sulla base del tipo riconosciuto grazie al corrispondente oggetto NfcTagConnection.

L’esempio mostra che è necessaria una distinzione tra i tipi di tag (anche se entrambi Mifare), in quanto il Mifare Classic prevede un’operazione di autenticazione sul blocco a cui accedere (blocco 8 nell’esempio) che non è prevista dal Mifare Ultralight; i due tag sono diversi anche in termini di struttura della memoria interna.

static void nfc_TagLost(object sender, NfcTagEventArgs e)
{
 Debug.Print("LOST " + HexToString(e.Connection.ID));
}

static void nfc_TagDetected(object sender, NfcTagEventArgs e)
{
 Debug.Print("DETECTED " + HexToString(e.Connection.ID));

 byte[] data;

 switch (e.NfcTagType)
 {
 case NfcTagType.MifareClassic1k:

 NfcMifareTagConnection mifareConn = (NfcMifareTagConnection)e.Connection;
 mifareConn.Authenticate(MifareKeyAuth.KeyA, 0x08, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF });
 mifareConn.Read(0x08);

 data = new byte[16];
 for (byte i = 0; i < data.Length; i++)
 dataIdea = i;

 mifareConn.Write(0x08, data);

 mifareConn.Read(0x08);
 break;

 case NfcTagType.MifareUltralight:

 NfcMifareUlTagConnection mifareUlConn = (NfcMifareUlTagConnection)e.Connection;

 for (byte i = 0; i < 16; i++)
 {
 byte[] read = mifareUlConn.Read(i);
 }

 mifareUlConn.Read(0x08);

 data = new byte[4];
 for (byte i = 0; i < data.Length; i++)
 dataIdea = i;

 mifareUlConn.Write(0x08, data);

 mifareUlConn.Read(0x08);
 break;

 default:
 break;
 }
}

Conclusioni

Il progetto è appena all’inizio e spero di avere numerosi feedback da coloro che lo utilizzeranno per poterlo migliorare. La possibilità di realizzare un driver per un chip NFC differente ed integrarlo nell’architettura senza alterare i livelli superiori, può essere un punto di forza.

La lettura e la scrittura dei tag NFC è di tipo “raw” e non c’è supporto per NDEF (NFC Data Exchange Format) ma non c’è da preoccuparsi ! Il team di Mopius mi ha aggiunto come developer al progetto NDEF Library ed il mio obiettivo è quello di realizzarne il porting anche su .Net Micro Framework.

.Net Micro Framework : usiamo il bus I2C !

Alla pari dell’SPI, analizzato in un articolo precedente, l’I2C (Inter-Integrated Circuit) è un bus di comunicazione sincrono utilizzato per la connessione e lo scambio dati tra un microprocessore e le periferiche esterne; sviluppato dalla Philips, oggi NXP, è diventato uno standard “de facto”.

Descrizione del bus

l’I2C è anche noto come bus two-wire in quanto è caratterizzato a tutti gli effetti da due soli “fili” :

  • SDA (Serial Data Line) : linea per il trasferimento dati;
  • SCL (Serial CLock) : clock per il sincronismo nello scambio dati;

Le linee suddette sono sempre caratterizzate da una resistenza di pull-up che ha il compito di mantenere il segnale “alto” (1 logico) in condizioni di idle mentre i componenti interconnessi (master e slave) hanno il compito di abbassarne il livello per trasferire uno 0 logico e di rilasciarlo per riportarlo in idle e trasferire un 1 logico; questo comportamento è tipico delle linee open-drain.

7506.425px-I2C_svg_thumb_0C0994EE

Analogamente all’SPI, è possibile avere più slave connessi al bus ed un unico master con cui comunicare; la differenza principale è che non esiste un segnale di SS (Slave Select) ma il master seleziona lo slave con cui comunicare attraverso un indirizzamento. Infatti, il master trasmette l’indirizzo dello slave sulla linea SDA prima di iniziare il trasferimento dei dati veri e propri; tale indirizzo è tipicamente a 7 bit (fino a 128 slave) ma è prevista un’estensione fino a 10 bit (fino a 1024 slave).

Una caratteristiche fondamentale dell’I2C è che permette la presenza di più master sul bus a differenza dell’SPI (modalità multi master).

Il protocollo di comunicazione

Il protocollo di comunicazione è caratterizzato dai seguenti passi :

  1. START Condition : il master abbassa l’SDA tenendo ancora alto l’SCL per indicare la condizione di START allo slave e quindi l’inizio di una trasmissione;
  2. Indirizzamento : il master invia un byte (MSB first) sul bus, in cui i primi 7 bit rappresentano l’indirizzo dello slave con cui comunicare e l’ultimo bit indica il tipo di operazione da voler effettuare (0 = write, 1, = read);
  3. Slave acknowledge : se sul bus esiste uno slave con tale indirizzo, esso risponde con un bit di ACK (0 logico);
  4. Comunicazione : a questo punto, il master può inviare e/o ricevere dati dallo slave in maniera sincrona grazie al movimento dell’SCL. Per ogni byte scambiato è sempre previsto un ACK dalla controparte;
  5. STOP Condition : il master alza l’SCL tenendo ancora basso l’SDA per indicare la STOP condition allo slave e quindi il termine della trasmissione;

4762.798px-I2C_data_transfer_svg_thumb_763FAC90

Il clock è sempre pilotato dal master ma in alcuni casi lo slave può mantenerne il valore basso per introdurre del delay ed evitare che il master gli invii altri dati (magari ha bisogno di più tempo per elaborare i dati già ricevuti) : questa funzionalità si chiama “clock stretching”.

.Net Micro Framework : le classi per utilizzare il bus

Il .Net Micro Framework semplifica notevolmente l’utilizzo del bus I2C mediante la classe I2CDevice (namespace Microsoft.SPOT.Hardware, assembly Microsoft.SPOT.Hardware.dll), il cui costruttore prevede un parametro del tipo I2CDevice.Configuration per poter essere opportunamente configurata; tale configurazione permette di impostare :

  • Address : indirizzo dello slave con cui comunicare;
  • ClockRateKhz : frequenza del clock;

Tutti i parametri di configurazione suddetti sono sempre strettamente legati al device con il quale si intende comunicare e vanno ricercati all’interno del datasheet.

I2CDevice.Configuration config =
      new I2CDevice.Configuration(I2C_ADDRESS, I2C_CLOCK_RATE_KHZ);

I2CDevice i2c = new I2CDevice(config);

La classe I2CDevice mette a disposizione un unico metodo Execute() per poter effettuare una o più “transazioni” sul bus che rappresentano le operazioni di lettura e scrittura con lo slave. Tale metodo prevede in ingresso un array di oggetti I2CDevice.I2CTransaction ed un timeout. La classe I2CDevice.I2CTransaction è la classe base per la classe I2CDevice.I2CReadTransaction, nel caso di una transazione di lettura, e per la classe I2CDevice.I2CWriteTransaction, nel caso di una transazione di scrittura.

La creazione di un’istanza per ciascuna delle due classi suddette può essere effettuata attraverso i due seguenti metodi statici della classe I2CDevice :

  • CreateReadTransaction() : crea un’istanza della classe I2CDevice.I2CReadTransaction associando ad essa l’array di byte ricevuto in ingresso come buffer per la ricezione dati dallo slave (inizialmente vuoto);
  • CreateWriteTransaction() : crea un’istanza della classe I2CDevice.I2CWriteTransaction associando ad essa l’array di byte ricevuto in ingresso come buffer contenente i dati da trasmettere allo slave;

In definitiva, la procedura d’uso dell’I2C prevede di creare un array di “transazioni” di lettura e/o scrittura (ovviamente anche mixate) ed eseguire queste transazioni in un solo colpo, ritrovandosi i dati trasmessi allo slave ed i buffer di ricezione con i dati richiesti.

Immaginiamo di aver un componente I2C caratterizzato da una serie di registri interni e di voler leggere il contenuto di uno di essi. Questo tipo di comunicazione è caratterizzata da due “transazioni” I2C; la prima di scrittura per poter inviare allo slave l’indirizzo del registro da leggere (attenzione !! non parliamo dell’indirizzo dello slave stesso che viene inviato in precedenza) e la seconda di lettura per poterne leggere il contenuto.

byte[] write = { REG_ADDRESS };
byte[] read = new byte[1];

// create I2C write and read transaction
I2CDevice.I2CTransaction[] i2cTx = new I2CDevice.I2CTransaction[2];
i2cTx[0] = I2CDevice.CreateWriteTransaction(write);
i2cTx[1] = I2CDevice.CreateReadTransaction(read);

// execution
i2c.Execute(i2cTx, I2C_TIMEOUT);

Uno sguardo verso il basso : lo strato di HAL

Anche nel caso dell’I2C (come già visto per l’SPI), ciascun OEM deve implementare uno strato HAL (e PAL) che faccia da “ponte” tra la parte CLR (fino al codice managed ad alto livello) ed il particolare hardware sottostante.

Prendiamo come riferimento la board Netduino (generazione 1) che ha come microcontrollore un Atmel AT91. Scaricando i sorgenti del firmware (sono open source) dal sito ufficiale, possiamo individuare l’implementazione in codice managed C# della classe I2CDevice (Framework\Core\Native_Hardware\I2C.cs) all’interno della quale viene invocato il metodo GetI2CPins() sull’istanza corrente dell’HardwareProvider.

public I2CDevice(Configuration config)
{
 this.Config = config;

 HardwareProvider hwProvider = HardwareProvider.HwProvider;

 if (hwProvider != null)
 {
 Cpu.Pin scl;
 Cpu.Pin sda;

 hwProvider.GetI2CPins(out scl, out sda);

 if (scl != Cpu.Pin.GPIO_NONE)
 {
 Port.ReservePin(scl, true);
 }

 if (sda != Cpu.Pin.GPIO_NONE)
 {
 Port.ReservePin(sda, true);
 }
 }

 Initialize();

 m_disposed = false;
}

Dopo una serie di invocazioni a cascata attraverso il CLR fino all’implementazione dell’HAL, sarà invocato il metodo GetPins() sulla classe AT91_I2C_Driver(DeviceCode\Targets\Native\AT91\DeviceCode\AT91_I2C\AT91__I2C.cpp) che ritorna gli identificativi dei pin del processore associati alla porta I2C.

void AT91_I2C_Driver::GetPins(GPIO_PIN& scl, GPIO_PIN& sda)
{
 NATIVE_PROFILE_HAL_PROCESSOR_I2C();

 scl = AT91_TWI_SCL;
 sda = AT91_TWI_SDA;
}

Nel caso della board Netduino (generazione 2) che ha un processore STM32, la funzione di lettura dei pin I2C è I2C_Internal_GetPins() (DeviceCode\Targets\Native\STM32\DeviceCode\STM32_I2C\STM32_i2c_functions.cpp).

void I2C_Internal_GetPins(GPIO_PIN& scl, GPIO_PIN& sda)
{
 scl = I2Cx_SCL_Pin;
 sda = I2Cx_SDA_Pin;
}

Conclusione

Il bus I2C a differenza dell’SPI non è ovviamente full duplex essendo caratterizzato da una sola linea dati ed è anche più lento in termini di velocità. Il vantaggio principale è quello di non avere la complessità di un segnale di selezione dello slave e di poter lavorare in modalità multi master.

Il .Net Micro Framework permette di utilizzare questo bus con estrema semplicità con una sola classe ed il concetto di “transazioni” lettura/scrittura I2C in modo da eseguire la comunicazione in un solo “colpo”.

Molto presto vedrete un esempio reale di applicazione di questo bus (come per il bus SPI) con un managed driver che ho sviluppato per un chip NFC della NXP !

C# : tip per la risoluzione del “ambiguous reference”

Oggi mi si è riproposto un problema che da tempo non mi si presentava ed ho deciso di scrivere un “tip” per la risoluzione. Il problema in oggetto è l’errore di compilazione “ambiguous reference”.

Purtroppo, nel mio progetto uPLibrary su CodePlex ho una classe Utility nel nemespaceuPLibrary.Utilities ma ne esiste un’omonima nel namespace Microsoft.SPOT.Hardware del .Net Micro Framework. Ovviamente, questo comporta un errore di “ambiguous reference” al momento della compilazione.

2275.Immagine_thumb_58E4FDA2

La soluzione a questo problema prevede l’uso degli alias per i namespace o per le classi, per cui abbiamo due possibilità :

  • using uPUtility = uPLibrary.Utilities.Utility ossia dichiarare un alias per la classee quindi utilizzare uPUtility nel codice;
  • using uPUtilities = uPLibrary.Utilities ossia dichiarare un alias per il namespacee quindi utilizzare uPUtilities .Utility nel codice;

Channel9 : gli SDK sperimentali per .Net Micro Framework, Netduino ed AGENT smartwatch per Visual Studio 2013

1321.Cattura_thumb_6163592B

Ancora una volta, uno dei miei post sul .Net Micro Framework è protagonista a Coding4Funsu Channel9 nel periodico post del venerdì sui progetti basati sull’hardware.

Questa volta si fa riferimento agli SDK sperimentali rilasciati dalla Secret Labs per supportare il .Net Micro Framework, le board Netduino e l’AGENT smartwatch in Visual Studio 2013.

Come sempre non può che farmi piacere !!

🙂

.Net Micro Framework : SPI, il bus seriale ad alta velocità !

L’SPI (Serial Peripheral Interface) è un bus di comunicazione sincrono tipicamente utilizzato per il trasferimento dei dati tra un microcontrollore ed una periferica esterna (es. sensore, attuatore, memoria, SD card, …). Essendo sincrono, a differenza della tipica comunicazione seriale asincrona (UART), esso utilizza un segnale di clock per garantire il perfetto sincronismo nella trasmissione e ricezione tra le due controparti note come master e slave.

Descrizione del bus

Complessivamente, il bus SPI è caratterizzato dai seguenti segnali :

  • SCLK (Serial CLocK) : clock per il sincronismo nello scambio dati;
  • SS (Slave Select) : segnale di abilitazione dello slave (ricevente);
  • MOSI (MasterOut / SlaveIn) : linea dei dati utilizzata per la trasmissione dal master allo slave;
  • MISO (MasterIn / SlaveOut) : linea dei dati utilizzata per la trasmissione dallo slave al master;

Escludendo il segnale SS, che può essere gestito separatamente, il bus va considerato un 3-wires (bus a 3 fili).

0285_SPI_single_slave_thumb_01DE8F4A

Il master ha il compito di generare il segnale di clock utilizzato anche dallo slave. Quest’ultimo utilizza tale segnale per individuare gli istanti di tempo in cui campionare il dato presente sulla linea MOSI (dato da ricevere) oppure in cui settare un livello logico (0/1) sulla linea MISO (dato da trasmettere); il campionamento può essere configurato sul fronte di salita o discesa del clock (fase del clock, CPHA) così come lo stato “attivo” del clock stesso può essere impostato alto oppure basso (polarità del clock, CPOL).

0880_SPI_timing_diagram2_thumb_6F299592

In pratica, per ogni impulso di clock scandito dal master, lo slave fa due operazioni :

  • campiona il segnale sulla linea MOSI per acquisire un dato in ricezione;
  • imposta un livello logico (0/1) sulla linea MISO per inviare un dato;

Tutto ciò rende l’SPI un bus full duplex in modo che la trasmissione e ricezione possano avvenire in contemporanea; tipicamente però molti dispositivi (slave) lavorano in modalità half duplex.

Questo tipo di architettura permette di implementare i dispositivi SPI con un semplice shift register al proprio interno. In corrispondenza di ciascun colpo di clock in caso di ricezione, il bit letto sulla linea MOSI viene trasferito nel registro e poi shiftato; al contrario, in caso di trasmissione, ad ogni colpo di clock viene eseguito uno shift del registro ed il bit viene impostato sulla linea MISO.

2844_SPI_8-bit_circular_transfer_thumb_47831968

Il segnale SS è utilizzato dal master per poter attivare lo slave con il quale iniziare una sessione di comunicazione. Infatti, il bus SPI è pensato per avere un solo master ed uno o più slave, ciascuno dei quali può essere attivato con un segnale dedicato. Tipicamente il segnale SS è alto quando lo slave è disconnesso dal bus ma viene impostato al valore basso (attivo basso) dal master, quando quest’ultimo vuole comunicare con uno specifico slave. Al termine della comunicazione, il segnale viene riportato al valore alto.

0876_SPI_three_slaves_thumb_6DE4FCB3

Si evince che è necessario un SS per ciascuno slave presente sul bus e questo comporta la necessità di un numero crescente di pin sul master all’aumentare dei  device connessi. In molti casi questa soluzione non è praticabile e si utilizza la connessione a cascata “daisy chain”, sfruttando un  solo SS per tutti gli slave che però sono collegati tra loro attraverso le linee  dati (il MISO di uno slave va nel MOSI dello slave successivo).

6153_SPI_three_slaves_daisy_chained_thumb_2D42B044

In questa modalità, il dato trasmesso dal master viene propagato in cascata a tutti gli slave in colpi di clock successivi, ciò vuol dire che se abbiamo N slave, sono necessarie N sequenze di 8 impulsi di clock per poter trasferire un intero byte su tutti gli slave.

.Net Micro Framework : le classi per utilizzare il bus

il .Net Micro Framework, secondo la logica di astrazione che lo caratterizza, mette a disposizione la classe SPI (namespace Microsoft.SPOT.Hardware, assembly Microsoft.SPOT.Hardware.dll) per poter utilizzare questa tipologia di bus con un qualsiasi dispositivo che lo supporta. Per poter iniziare ad utilizzare questa funzionalità è necessario configurare la porta SPI da utilizzare attraverso la classe innestata SPI.Configuration; tale configurazione permette di impostare :

  • ChipSelect_Port : il pin (enumerativo Cpu.Pin) che sarà utilizzato come SS (Slave Select). E’ possibile impostare il valore GPIO_NONE se si preferisce pilotare questo pin direttamente senza lasciare l’onere alla classe SPI;
  • ChipSelect_ActiveState : lo stato attivo del chip select. Tipicamente i device SPI hanno un chip select “attivo basso”, ossia è necessario impostare il livello logico 0 (false) per attivare e comunicare con il device;
  • ChipSelect_SetupTime : è il tempo che deve intercorrere dall’istante in cui viene attivato il chip select ed il segnale di clock viene trasmesso sulla relativa linea. E’ un parametro strettamente legato al device con cui si comunica (vedi datasheet), perchè è il tempo che impiega il device per “accorgersi” che è stato attivato e che il master vuole parlare con lui;
  • ChipSelect_HoldTime : è il tempo che deve intercorrere tra la fine della transazione di lettura/scrittura e l’istante in cui il chip select viene disattivo. In pratica, serve a far completare allo slave l’operazione per poi essere disattivato (anche in questo caso dipende dal device e va ricercato nel datasheet);
  • Clock_IdleState : indica la condizione di idle del clock presente sulla linea quando lo slave non è stato attivato; è tipicamento noto come polarità del clock;
  • Clock_Edge : indica il fronte di salita o discesa in corrispondenza del quale il dato sulla linea di comunicazione (MISO o MOSI) viene campionato; è tipicamente noto come fase del clock;
  • Clock_Rate : è la frequenza del clock;
  • SPI_mod : rappresenta l’enumerativo SPI.SPI_module che indica la porta SPI fisica del processore da adottare;

Tutti i parametri di configurazione suddetti sono sempre strettamente legati al device con il quale si intende comunicare e vanno ricercati all’interno del datasheet. Per quanto riguarda il parametro SPI_mod, va invece ricercato nella documentazione del master (tipicamente la CPU della nostra board) per individuare in che modo l’OEM ha esposto le porte SPI disponibili attraverso l’HAL del .Net Micro Framework.

Un’istanza della classe SPI.Configuration va passata come parametro al costruttore della classe SPI per poter iniziare subito ad utilizzare il bus.

SPI.SPI_module spiModule = SPI.SPI_module.SPI1;

SPI.Configuration spiCfg = new SPI.Configuration(Cpu.Pin.GPIO_NONE,     // chip select pin
                                                 SPI_CS_ACTIVE_STATE,   // chip select active state
                                                 SPI_CS_SETUP_TIME,     // chip select setup time
                                                 SPI_CS_HOLD_TIME,      // chip select hold time
                                                 SPI_CLK_IDLE_STATE,    // clock idle state
                                                 SPI_CLK_EDGE,          // clock edge
                                                 SPI_CLK_RATE,          // clock rate (Khz)
                                                 spiModule);            // spi module used

SPI spi = new SPI(spiCfg);

OutputPort nssPort = new OutputPort(Cpu.Pin.GPIO_Pin0, true);

Nel codice riportato in alto, si preferisce gestire il segnale SS in maniera autonoma mediante l’uso di una OutputPort per muovere un pin corrispondente.

Una volta disponibile un’istanza della classe SPI, i metodi principali utilizzabili sono solo due :

  • Write() : permette di eseguire un trasferimento dati dal master allo slave. Fornisce due overload per permettere l’operazione a blocchi di 8 o 16 bit (un array di byte o ushort);
  • WriteRead() : permette di eseguire un trasferimento dati dal master allo slave e viceversa. Tale operazione avviene in contemporanea essendo l’SPI full duplex; anche in questo caso è possibile trasferire blocchi da 8 o 16 bit;

Il metodo di Write() è concettualmente semplice, in quanto la classe si fa carico di muovere il segnale di clock trasferendo sulla linea i dati nell’array che riceve come parametro. Per il metodo di WriteRead() bisogna fare una precisazione : l’array (inizialmente vuoto) in cui verranno messi i dati ricevuti dallo slave deve avere la stessa dimensione dell’array che contiene i dati da trasmettere. Questa uguaglianza è necessaria per la caratteristica intrinseca del bus SPI sul quale ad ogni colpo di clock viene trasmesso un bit del buffer di invio e viene acquisito un bit per il buffer di ricezione.

byte[] write = new byte[CMD_SIZE];

// prepare write buffer ...

// send frame
nssPort.Write(false);
spi.Write(write);
nssPort.Write(true);

Nell’esempio precedente, il segnale di SS viene impostato a false prima di eseguire laWrite() tramite la classe SPI in modo da abilitare lo slave a ricevere i dati; viene riportato a true al termine della trasmissione.

Osserviamo che non esiste un metodo di Read() ! Come mai ? Se volessimo solo leggere dallo slave senza dover inviare nulla. Tipicamente i device SPI prevedono sempre un comando da dover trasmettere per poi iniziare a ricevere dei dati, quindi nella maggior parte dei casi ci ritroviamo a dover usare la WriteRead(). E’ pur vero, però, che lo slave deve prima ricevere il comando per poterlo analizzare, eseguire l’operazione e rispondere con un dato, per cui è impossibile che in corrispondenza dei colpi di clock di trasmissione del comando, il master inizia a ricevere anche la risposta. In moltissimi casi, il metodo di Write()viene usato per trasmettere il comando e viene seguito da un WriteRead() per leggere la risposta. In quest’ultimo caso, cosa dobbiamo scrivere sul bus se siamo solo interessati a ricevere ? Ebbene la risposta è semplice … inviamo dei “dummy” bytes ! In pratica, utilizziamo la WriteRead() per leggere un dato dallo slave grazie al fatto che la classe SPI  genera in automatico i colpi di clock per la ricezione stessa; non dovendo trasmettere niente, impostiamo la linea MOSI in uno stato di idle (alta o bassa, usando i byte 0xFF o 0X00) oppure con un “dummy” byte qualsiasi, purché dal datasheet del device quest’ultimo non dia “fastidio” allo slave.

// dummy bytes from master to force clock a reading from slave
byte[] write = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
byte[] read = new byte[5];

nssPort.Write(false);
// write dummy bytes to read data
spi.WriteRead(write, read);
nssPort.Write(true);

Uno sguardo verso il basso : lo strato di HAL

Secondo l’architettura del .Net Micro Framework, ciascun OEM deve implementare uno strato HAL (e PAL) che faccia da “ponte” tra la parte CLR (fino al codice managed ad alto livello) ed il particolare hardware sottostante.

Prendiamo come riferimento la board Netduino (generazione 1) che ha come microcontrollore un Atmel AT91. Scaricando i sorgenti del firmware (sono open source) dalsito ufficiale, possiamo individuare l’implementazione in codice managed C# della classe SPI(Framework\Core\Native_Hardware\SPI.cs) all’interno della quale viene invocato il metodoGetSpiPins() sull’istanza corrente dell’HardwareProvider; fornendo l’identificativo dell’SPI module, tale metodo ritorna i pin relativi all’SCK, MISO e MOSI. Qualora avessimo specificato un pin per il chip select nel costruttore, esso crea anche una istanza OutputPort per quet’ulitmo (è in pratica l’operazione che faremmo noi qualora volessimo pilotare l’SS in autonomia e passassimo GPIO_NONE al costruttore dell’SPI.

   1: public SPI(Configuration config)
   2: {
   3:     HardwareProvider hwProvider = HardwareProvider.HwProvider;
   4:
   5:     if (hwProvider != null)
   6:     {
   7:         Cpu.Pin msk;
   8:         Cpu.Pin miso;
   9:         Cpu.Pin mosi;
  10:
  11:         hwProvider.GetSpiPins(config.SPI_mod, out msk, out miso, out mosi);
  12:
  13:         if (msk != Cpu.Pin.GPIO_NONE)
  14:         {
  15:             Port.ReservePin(msk, true);
  16:         }
  17:
  18:         if (miso != Cpu.Pin.GPIO_NONE)
  19:         {
  20:             Port.ReservePin(miso, true);
  21:         }
  22:
  23:         if (mosi != Cpu.Pin.GPIO_NONE)
  24:         {
  25:             Port.ReservePin(mosi, true);
  26:         }
  27:     }
  28:
  29:     if (config.ChipSelect_Port != Cpu.Pin.GPIO_NONE)
  30:     {
  31:         m_cs = new OutputPort(config.ChipSelect_Port, !config.ChipSelect_ActiveState);
  32:     }
  33:
  34:     m_config = config;
  35:     m_disposed = false;
  36: }

Dopo una serie di invocazioni a cascata attraverso il CLR fino all’implementazione dell’HAL, sarà invocato il metodo GetPins() sulla classe AT91_SPI_Driver(DeviceCode\Targets\Native\AT91\DeviceCode\AT91_SPI\AT91__SPI.cpp) che ritorna gli identificativi dei pin del processore associati alla porta SPI richiesta. Si osserva che il Netduino permette di utilizzare solo la porta indicata con 0, associata ai pin digitali 11, 12 e 13 rispettivamente per MOSI, MISO e SCLK.

void AT91_SPI_Driver::GetPins(UINT32 spi_mod, GPIO_PIN &msk, GPIO_PIN &miso, GPIO_PIN &mosi)
{
    NATIVE_PROFILE_HAL_PROCESSOR_SPI();

    switch(spi_mod)
    {
    case 0:
        msk = AT91_SPI0_SCLK;
        miso = AT91_SPI0_MISO;
        mosi = AT91_SPI0_MOSI;

        break;
#if (AT91C_MAX_SPI == 2)
    case 1:
        msk = AT91_SPI1_SCLK;
        miso = AT91_SPI1_MISO;
        mosi = AT91_SPI1_MOSI;

        break;
#endif
    default:
        break;

    }
}

Nel caso della board Netduino (generazione 2) che ha un processore STM32, la funzione di lettura dei pin SPI è CPU_SPI_GetPins()(DeviceCode\Targets\Native\STM32\DeviceCode\STM32_SPI\STM32_SPI_functions.cpp) che viene invocata ogni qual volta si avvia e ferma una trasmissione con lo slave.

void CPU_SPI_GetPins( UINT32 spi_mod, GPIO_PIN& msk, GPIO_PIN& miso, GPIO_PIN& mosi )
{
    NATIVE_PROFILE_HAL_PROCESSOR_SPI();
    if (spi_mod == 0) {
#if defined(PLATFORM_ARM_Netduino2) || defined(PLATFORM_ARM_NetduinoPlus2) || defined(PLATFORM_ARM_NetduinoShieldBase)
        msk  = SPI2_SCLK_Pin;
        miso = SPI2_MISO_Pin;
        mosi = SPI2_MOSI_Pin;
#else
        msk  = SPI1_SCLK_Pin;
        miso = SPI1_MISO_Pin;
        mosi = SPI1_MOSI_Pin;
#endif
    } else if (spi_mod == 1) {
#if defined(PLATFORM_ARM_Netduino2) || defined(PLATFORM_ARM_NetduinoPlus2) || defined(PLATFORM_ARM_NetduinoShieldBase)
        msk  = SPI1_SCLK_Pin;
        miso = SPI1_MISO_Pin;
        mosi = SPI1_MOSI_Pin;
#else
        msk  = SPI2_SCLK_Pin;
        miso = SPI2_MISO_Pin;
        mosi = SPI2_MOSI_Pin;
#endif
    } else {
        msk  = SPI3_SCLK_Pin;
        miso = SPI3_MISO_Pin;
        mosi = SPI3_MOSI_Pin;
    }
}

Un aspetto importante da sottolineare è che l’SPI del Netduino shifta e trasmette i dati in uscita nella modalità MSB first, ossia inizia la trasmissione dal bit più significativo (Most Significant Bit). Nel caso in cui il device si aspetta di ricevere i bit nell’ordine opposto (LSB, Least Significant Bit), è necessario invertire l’ordine dei bit prima di avviare la trasmissione.

Conclusione

Il bus SPI ha il vantaggio di essere full duplex ma soprattutto di lavorare a velocità elevatissime. Inoltre, l’implementazione hardware di un device che lo supporti è relativamente semplice. Gli svantaggi principali sono la necessità di più pin (per gli SS) e l’assenza di un controllo di flusso hardware oltre che di un acknoweledge dallo slave (che va implementato a livello software e di protocollo superiore).

Il .Net Micro Framework permette di utilizzare questo bus con estrema semplicità con una sola classe e due metodi principali. In questo modo, è possibile realizzare un managed driver per poter comunicare con un qualsiasi dispositivo SPI.

Molto preso scriverò un post su un managed driver che sto sviluppando per un chip NFC della NXP che utilizza il bus SPI (oltre che supportare I2C e HSU), per toccare con mano le potenzialità di sviluppo di questo splendido framework !