Codice di esempio Arduino per gli encoder assoluti SPI

Di Damon Tarry, Design Applications Engineer, Same Sky

Questo tutorial sul codice di esempio di Arduino intende fornire agli utenti un solido punto di partenza per la configurazione e la lettura dei dati dagli encoder assoluti AMT22 di Same Sky con comunicazione tramite interfaccia periferica seriale (SPI). Il tutorial illustra l'hardware e il software necessari, i principali requisiti di configurazione e i pacchetti di codice e le istruzioni di esempio per le opzioni di uscita monogiro e multigiro. Ecco un elenco dell'occorrente per iniziare:

Panoramica dell'encoder assoluto AMT22

L'encoder AMT22 di Same Sky (ex CUI Devices) è un encoder assoluto con risoluzione a 12 o 14 bit, che fornisce un numero preciso di posizioni uniche per rivoluzione. Per la variante a 12 bit, ciò si traduce in 4.096 posizioni distinte, mentre il modello a 14 bit offre 16.384 posizioni per rivoluzione. Indipendentemente dal numero di rotazioni, il dispositivo segnala continuamente la sua posizione assoluta, fornendo agli utenti un riscontro preciso sull'angolo esatto del dispositivo.

Questo encoder è disponibile nei modelli monogiro e multigiro. La variante monogiro misura la posizione all'interno di una singola rotazione di 360 gradi, mentre la versione multigiro traccia non solo la posizione all'interno di una rotazione, ma anche il numero totale di rotazioni complete. Inoltre, le varianti monogiro sono dotate di un punto zero programmabile, che consente agli utenti di definire un riferimento personalizzato per l'origine dell'encoder.

Primi passi

Assicurarsi che il dispositivo sia in modalità RUN regolando l'interruttore posto sul retro dell'encoder nella posizione appropriata (Figura 1). Montare l'encoder AMT22 su un motore o su un gruppo utilizzando le istruzioni di montaggio AMT per garantire un'installazione corretta. AMT22 supporta 9 dimensioni dell'albero, da 2 mm a 8 mm.

Schema dell'encoder AMT22 di Same Sky commutato in modalità RUNFigura 1: Portare l'interruttore sul retro dell'encoder AMT22 in modalità RUN. (Immagine per gentile concessione di Same Sky)

I collegamenti indicati nella Figura 2 e nella Tabella 1 sono specifici per la scheda Arduino Uno, ma il codice fornito dovrebbe essere compatibile con la maggior parte delle schede Arduino. Tuttavia, si tenga presente che le configurazioni dei pin possono differire tra i vari modelli Arduino. Per i dettagli precisi sui collegamenti di altre schede, si consiglia di consultare la documentazione Arduino corrispondente.

Schema dei collegamenti del cablaggio di Arduino Uno con l'encoder AMT22Figura 2: Collegamenti del cablaggio di Arduino Uno con l'encoder AMT22. (Immagine per gentile concessione di Same Sky)

Funzione Numero di pin dell'encoder Pin Arduino Uno AMT-DBC-1-036
+5 V 1 5 V Bianco/verde
SCLK 2 13 Blu/bianco
MOSI 3 11 Bianco/blu
GND 4 GND Verde/bianco
MISO 5 12 Arancione/bianco
CS 6 2 Bianco/arancione

Tabella 1: Collegamenti del cablaggio di Arduino Uno ulteriormente definiti. (Immagine per gentile concessione di Same Sky)

L'encoder AMT22 inizia a trasmettere i dati di posizione assoluta subito dopo l'inizio della comunicazione SPI, eliminando la necessità di una struttura tradizionale di comando-risposta. Durante il primo byte del trasferimento SPI, l'host invia 0x00 e l'encoder AMT22 risponde contemporaneamente con dati di posizione validi.

Se l'host deve inviare un comando (Tabella 2), ad esempio un comando di azzeramento, lo farà nel secondo byte della trasmissione. Questo viene definito un comando esteso. Per le specifiche tecniche dettagliate, consultare la scheda tecnica AMT22.

Comando Byte Note
Ricevi posizione 0x00 0x00
Imposta zero 0x00 0x70 Solo monogiro
Ricevi giri 0x00 0xA0 Solo multigiro

Tabella 2: Comandi AMT22 definiti. (Immagine per gentile concessione di Same Sky)

Tutorial del codice - inclusioni e definizioni

Poiché il bus SPI di Arduino viene utilizzato per interfacciarsi con l'encoder AMT22, è necessario includere nel codice la libreria SPI. Per inviare i dati di posizione da Arduino al computer viene utilizzata la connessione USB-seriale integrata nell'IDE Arduino, configurata con una velocità di trasmissione di 115200.

Inoltre, è necessario definire i comandi utilizzati dall'encoder AMT22. Poiché l'encoder non elabora il contenuto del primo byte, viene assegnato un NOP (non operazione) per semplificare il processo di comunicazione (listato 1).

Copy
/* Include the SPI library for the arduino boards */
#include <SPI.h>
 
/* Serial rates for UART */
#define BAUDRATE      115200
 
/* SPI commands */
#define AMT22_NOP     0x00
#define AMT22_ZERO    0x70
#define AMT22_TURNS   0xA0

Listato 1: Impostazione dell'interfaccia SPI.

Inizializzazione

Nella funzione setup() (listato 2), si comincia inizializzando tutti i pin SPI richiesti e configurando le interfacce seriali per la comunicazione.

La porta seriale deve essere inizializzata per consentire la trasmissione dei dati al computer host. Ciò avviene passando il BAUDRATE definito nella funzione Serial.begin().

Prima di abilitare SPI, assicurarsi che la riga chip-select (CS) sia impostata sullo stato appropriato per preparare l'encoder alla comunicazione.

Selezionare una frequenza di clock per il bus SPI per comunicare con AMT22. A fini di prototipazione, è adatta una frequenza di clock di 500 kHz, anche se AMT22 supporta frequenze fino a 2 MHz. Per ottenere 500 kHz è possibile utilizzare l'impostazione SPI_CLOCK_DIV32. Considerando il clock a 16 MHz di Arduino Uno, questa divisione risulta in una frequenza di clock SPI di 500 kHz. Per ulteriori dettagli sulla configurazione del clock SPI, consultare la documentazione di Arduino.

Dopo aver configurato il tutto, il bus SPI può essere inizializzato utilizzando SPI.begin(), che imposterà i tre pin SPI dedicati: MISO, MOSI e SCLK, preparando il sistema alla comunicazione con l'encoder.

Copy
void setup()
{
  uint8_t cs_pin = 2;
 
  //Set the modes for the SPI CS
  pinMode(cs_pin, OUTPUT);
  //Get the CS line high which is the default inactive state
  digitalWrite(cs_pin, HIGH);
 
  //Initialize the UART serial connection for debugging
  Serial.begin(BAUDRATE);
 
  //set the clockrate. Uno clock rate is 16Mhz, divider of 32 gives 500 kHz.
  //500 kHz is a good speed for our test environment
  //SPI.setClockDivider(SPI_CLOCK_DIV2);   // 8 MHz
  //SPI.setClockDivider(SPI_CLOCK_DIV4);   // 4 MHz
  //SPI.setClockDivider(SPI_CLOCK_DIV8);   // 2 MHz
  //SPI.setClockDivider(SPI_CLOCK_DIV16);  // 1 MHz
  SPI.setClockDivider(SPI_CLOCK_DIV32);    // 500 kHz
  //SPI.setClockDivider(SPI_CLOCK_DIV64);  // 250 kHz
  //SPI.setClockDivider(SPI_CLOCK_DIV128); // 125 kHz
 
  //start SPI bus
  SPI.begin();
}

Listato 2: La funzione setup() che inizializza tutti i pin SPI.

Comunicazione SPI

La comunicazione SPI con AMT22 è gestita dalla libreria SPI di Arduino, mentre il controllo chip-select (CS) è gestito dal codice tramite i pin I/O digitali. La funzione digitalWrite() viene utilizzata per asserire o deasserire la riga CS (listato 3).

AMT22 si aspetta l'invio di due byte 0x00 e restituisce i dati subito dopo averli ricevuti. A causa della rapidità della risposta, è necessario rispettare alcuni requisiti minimi di temporizzazione, descritti nella scheda tecnica AMT22.

Indipendentemente dalla versione a 12 o 14 bit, l'encoder risponde sempre con due byte (16 bit) di dati. I due bit superiori sono bit di controllo, utilizzati per verificare l'integrità dei dati. Per la versione a 12 bit, i due bit inferiori sono entrambi 0 e il valore restituito deve essere spostato a destra di 2 bit (o diviso per 4) per essere utilizzato correttamente.

Per ottenere i dati di posizione, viene richiamata la funzione SPI.transfer() inviando il comando AMT22_NOP. Durante questo processo, la riga CS rimane bassa. AMT22 invia per primo il byte alto, quindi il byte ricevuto viene spostato a sinistra di 8 bit per allinearlo nella metà superiore di una variabile uint16_t. Questo valore viene assegnato alla variabile encoderPosition in un'unica operazione. Dopo un breve ritardo per soddisfare i requisiti di temporizzazione, viene eseguita una seconda chiamata SPI.transfer() per inviare un altro comando AMT22_NOP. Il risultato viene sottoposto a OR con il valore corrente in encoderPosition, combinando di fatto i due byte ricevuti in una singola variabile uint16_t. Infine, la riga CS viene rilasciata, completando la comunicazione.

Copy
uint8_t cs_pin = 2;
 
//set the CS signal to low
digitalWrite(cs_pin, LOW);
delayMicroseconds(3);
 
//read the two bytes for position from the encoder, starting with the high byte
uint16_t encoderPosition = SPI.transfer(AMT22_NOP) << 8; //shift up 8 bits because this is the high byte
delayMicroseconds(3);
encoderPosition |= SPI.transfer(AMT22_NOP); //we do not need a specific command to get the encoder position, just no-op
 
//set the CS signal to high
digitalWrite(cs_pin, HIGH);

Listato 3: Impostazione della comunicazione SPI.

Verifica del checksum

Dopo aver completato il trasferimento SPI, è essenziale convalidare i dati ricevuti utilizzando un checksum (Listato 4).

Per implementare questa validazione, è possibile creare una funzione basata sull'equazione fornita nella scheda tecnica. Il checksum è contenuto nei due bit superiori del valore ricevuto e utilizza la parità dispari tra i bit pari e dispari della risposta di posizione.

La funzione esegue le seguenti operazioni:

  1. Calcolo della parità per i bit dispari (bit 1, 3, 5, 7, 9, 11, 13)
  2. Calcolo della parità per i bit pari (bit 0, 2, 4, 6, 8, 10, 12, 14)
  3. Confronta le parità calcolate con i valori indicati dai bit di checksum

La funzione restituisce True se il checksum è valido, indicando che l'integrità dei dati è confermata. Se il checksum non è valido, la funzione restituisce False, segnalando un potenziale errore nei dati ricevuti.

Copy
/*
 * Using the equation on the datasheet we can calculate the checksums and then make sure they match what the encoder sent.
 */
bool verifyChecksumSPI(uint16_t message)
{
  //checksum is invert of XOR of bits, so start with 0b11, so things end up inverted
  uint16_t checksum = 0x3;
  for(int i = 0; i < 14; i += 2)
  {
    checksum ^= (message >> i) & 0x3;
  }
  return checksum == (message >> 14);
}

Listato 4: Convalida del checksum.

Formattazione dei dati

Se la convalida del checksum conferma l'integrità dei dati, il passo successivo consiste nell'aggiornare la variabile encoderPosition rimuovendo i due bit superiori (Listato 5). A tale fine si può applicare un'operazione bitwise AND con 0x3FFF (o 0b001111111111111111), che conserva di fatto tutti i 14 bit inferiori dei dati di posizione.

Inoltre, è necessario tenere conto della risoluzione dell'encoder, che può essere a 12 o 14 bit. Se la risoluzione è di 12 bit, il valore di encoderPosition deve essere spostato di 2 bit a destra per adattarsi alla risoluzione inferiore. Ciò garantisce che i dati di posizione siano rappresentati con precisione nella variabile encoderPosition, che riflette la posizione effettiva dell'encoder in base alla risoluzione specificata.

Copy
if (verifyChecksumSPI(encoderPosition)) //position was good
{
  encoderPosition &= 0x3FFF; //discard upper two checksum bits
  if (RESOLUTION == 12) encoderPosition = encoderPosition >> 2; //on a 12-bit encoder, the lower two bits will always be zero
 
  Serial.print(encoderPosition, DEC); //print the position in decimal format
  Serial.write('\n');
}
else //position is bad
{
  Serial.print("Encoder position error.\n");
}

Listato 5: Aggiornamento di encoderPosition.

Impostazione della posizione zero (solo monogiro)

Alcune varianti dell'encoder AMT22 offrono una funzione di posizione zero programmabile. Per impostare questa posizione zero, è necessario inviare una specifica sequenza di comandi a due byte. La procedura prevede l'invio del comando AMT22_NOP, seguito da una breve attesa per soddisfare i requisiti minimi di temporizzazione specificati dall'encoder AMT22. Trascorso questo tempo di attesa, viene inviato il comando AMT22_ZERO assicurando il rilascio della riga chip-select (CS). Una volta ricevuto questo comando, l'encoder esegue un'operazione di reset (Listato 6).

Per evitare la comunicazione con l'encoder durante questo periodo di reset, viene implementato un ritardo di 250 ms, che assicura che non vengano inviati comandi all'encoder durante il tempo di accensione.

Sebbene sia possibile che il codice imposti la posizione zero dell'encoder all'inizio del funzionamento, nelle applicazioni tipiche è più comune impostarla solo una volta durante la configurazione iniziale del dispositivo per l'uso all'interno del sistema. Questa pratica contribuisce a mantenere l'integrità della retroazione di posizione dell'encoder per tutta la sua durata prevista.

Copy
/*
 * The AMT22 bus allows for extended commands. The first byte is 0x00 like a normal position transfer,
 * but the second byte is the command.
 * This function takes the pin number of the desired device as an input
 */
void setZeroSPI(uint8_t cs_pin)
{
  //set CS to low
  digitalWrite(cs_pin, LOW);
  delayMicroseconds(3);
 
  //send the first byte of the command
  SPI.transfer(AMT22_NOP);
  delayMicroseconds(3);
 
  //send the second byte of the command
  SPI.transfer(AMT22_ZERO);
  delayMicroseconds(3);
 
  //set CS to high
  digitalWrite(cs_pin, HIGH);
 
  delay(250); //250 millisecond delay to allow the encoder to reset
}

Listato 6: Impostazione della posizione zero di un encoder AMT22 monogiro.

Lettura del contagiri (solo multigiro)

Alcune varianti dell'encoder AMT22 supportano un contatore multigiro, che consente agli utenti di leggere sia la posizione che il conteggio dei giri in un'unica sequenza di recupero dei dati.

Se i dati di posizione ricevuti non sono validi, il sistema deve notificare all'utente l'errore. Invece, se la posizione è valida, il programma deve riportare la posizione in formato decimale (Listato 7). Questa capacità migliora la funzionalità dell'encoder fornendo una retroazione completa sia sulla posizione assoluta sia sul numero di giri completi, facilitando un monitoraggio e un controllo più accurati nelle applicazioni che richiedono dati rotazionali precisi.

Copy
uint8_t cs_pin = 2;
 
//set the CS signal to low
digitalWrite(cs_pin, LOW);
delayMicroseconds(3);
 
//read the two bytes for position from the encoder, starting with the high byte
uint16_t encoderPosition = SPI.transfer(AMT22_NOP) << 8; //shift up 8 bits because this is the high byte
delayMicroseconds(3);
encoderPosition |= SPI.transfer(AMT22_TURNS); //we send the turns command (0xA0) here, to tell the encoder to send us the turns count after the position
 
//wait 40us before reading the turns counter
delayMicroseconds(40);
 
//read the two bytes for turns from the encoder, starting with the high byte
uint16_t encoderTurns = SPI.transfer(AMT22_NOP) << 8; //shift up 8 bits because this is the high byte
delayMicroseconds(3);
encoderTurns |= SPI.transfer(AMT22_NOP);
delayMicroseconds(3);
 
//set the CS signal to high
digitalWrite(cs_pin, HIGH);

Listato 7: Lettura di encoderPosition e del contagiri in un encoder AMT22 multigiro.

Esecuzione del codice

Una volta creato il codice, è il momento di caricarlo su Arduino e stabilire la comunicazione con l'encoder AMT22.

Per monitorare l'uscita, aprire il monitor seriale nell'IDE Arduino e assicurarsi che la velocità dei dati sia impostata su 115200 baud. In questo modo gli utenti potranno osservare il funzionamento dell'encoder e visualizzare i dati di posizione riportati in tempo reale. Una volta che il monitor seriale è attivo, l'encoder dovrebbe iniziare a trasmettere le informazioni sulla posizione, dimostrando la sua funzionalità all'interno del sistema (Figura 3).

Immagine della posizione riportata dall'encoderFigura 3: La posizione riportata dall'encoder, ricevuta da Arduino (Immagine per gentile concessione di Same Sky)

Encoder multipli

Un vantaggio significativo dell'uso di un dispositivo SPI è la possibilità di comunicare con più encoder sullo stesso bus. A tal fine, è necessario allocare un pin I/O digitale aggiuntivo per ciascun encoder, in modo da consentire il controllo individuale di chip-select (CS).

Nel codice di esempio (Listato 8), viene utilizzato un array di pin CS per supportare un numero arbitrario di encoder. Ciò consente una comunicazione scalabile, permettendo all'utente di aggiungere facilmente altri encoder in base alle necessità. Modificando le funzioni per accettare il numero di pin corrispondente al dispositivo desiderato, il codice può controllare dinamicamente quale encoder sia attivo sul bus SPI, garantendo l'accesso e il funzionamento indipendente di ciascun dispositivo.

Copy
uint8_t cs_pins[] = {2}; //only one encoder connected, using pin 2 on arduino for CS
//uint8_t cs_pins[] = {2, 3}; //two encoders connected, using pins 2 & 3 on arduino for CS

Listato 8: Impostazione di un array per la lettura di più encoder.

Il passo successivo consiste nell'eseguire un ciclo su ciascun pin CS dell'array e leggere la posizione da ciascun encoder collegato. In questo modo, il sistema può attivare ciascun encoder asserendo la sua riga chip-select, eseguendo il trasferimento SPI e recuperando i dati di posizione. Il codice selezionerà in sequenza ogni encoder, eseguirà la comunicazione SPI e rilascerà la riga CS, assicurando che tutti i dispositivi collegati vengano interrogati per ricavarne le rispettive informazioni di posizione (Listato 9).

Copy
void loop()
{
  for(int encoder = 0; encoder < sizeof(cs_pins); ++encoder)
  {
    uint8_t cs_pin = cs_pins[encoder];
 
    //set the CS signal to low
    digitalWrite(cs_pin, LOW);
    delayMicroseconds(3);
 
    //read the two bytes for position from the encoder, starting with the high byte
    uint16_t encoderPosition = SPI.transfer(AMT22_NOP) << 8; //shift up 8 bits because this is the high byte
    delayMicroseconds(3);
    encoderPosition |= SPI.transfer(AMT22_NOP); //we do not need a specific command to get the encoder position, just no-op
 
    //set the CS signal to high
    digitalWrite(cs_pin, HIGH);
 
    if (verifyChecksumSPI(encoderPosition)) //position was good, print to serial stream
    {
      encoderPosition &= 0x3FFF; //discard upper two checksum bits
      if (RESOLUTION == 12) encoderPosition = encoderPosition >> 2; //on a 12-bit encoder, the lower two bits will always be zero
 
      Serial.print("Encoder #");
      Serial.print(encoder, DEC);
      Serial.print(" position: ");
      Serial.print(encoderPosition, DEC); //print the position in decimal format
      Serial.write('\n');
    }
    else //position is bad, let the user know how many times we tried
    {
      Serial.print("Encoder #");
      Serial.print(encoder, DEC);
      Serial.print(" position error.\n");
    }
  }
 
  //For the purpose of this demo we don't need the position returned that quickly so let's wait a half second between reads
  //delay() is in milliseconds
  delay(500);
}

Listato 9: Lettura della variabile encoderPosition da più encoder.

Ultimato il trasferimento dei dati, è necessario un tempo di attesa minimo prima di rilasciare la riga chip-select. Secondo la scheda tecnica, questo tempo minimo è di 3 microsecondi. Sebbene questo ritardo sia tipicamente osservato in modo naturale alle velocità di trasmissione dei dati più basse, è buona norma implementarlo esplicitamente nel codice per garantire il corretto funzionamento e l'aderenza alle specifiche di temporizzazione. Ciò garantisce una comunicazione affidabile con l'encoder AMT22.

Conclusione

Gli utenti dovrebbero ora avere una conoscenza di base della configurazione e della lettura dei dati degli encoder assoluti AMT22 di Same Sky. Questo articolo si è concentrato sugli encoder assoluti AMT22. Same Sky offre anche una linea di encoder modulari AMT con una serie di versioni incrementali, assolute e a commutazione.

Esonero della responsabilità: le opinioni, le convinzioni e i punti di vista espressi dai vari autori e/o dai partecipanti al forum su questo sito Web non riflettono necessariamente le opinioni, le convinzioni e i punti di vista di DigiKey o le sue politiche.

Informazioni su questo autore

Damon Tarry, Design Applications Engineer, Same Sky