SPI 絕對編碼器的 Arduino 範例程式碼

作者:Damon Tarry, Design Applications Engineer, Same Sky

這份 Arduino 範例程式碼教學的目標是為使用者建立穩固的起點,以便透過序列周邊介面 (SPI) 通訊,對 Same SkyAMT22 絕對編碼器進行配置並從中讀取資料。此教學將指出所需的軟硬體、關鍵的設置要求,以及單匝和多匝輸出選項的範例程式碼套裝與指示。以下是開始動手所需的品項清單:

AMT22 絕對編碼器概述

Same Sky (前身為 CUI Devices) 的 AMT22 是一款絕對編碼器,提供 12 或 14 位元的解析度,也就是每轉一圈可提供精確的唯一位置數。若是 12 位元款式,就可提供 4,096 個不同的位置,而 14 位元款式則每轉一圈可提供 16,384 個位置。無論裝置旋轉多少次,都會持續回報其絕對位置,因此使用者可準確得知裝置的確切角度。

此編碼器提供單匝和多匝型號。單匝款式會測量單次 360 度旋轉內的位置,而多匝款式不僅會追蹤旋轉內的位置,也會追蹤完整旋轉的總次數。此外,單匝款式具有可編程的零點,能讓使用者針對編碼器的原點自訂參考基準。

開始使用

可將編碼器背面的開關調整到適當的位置,確保裝置處於 RUN 模式 (圖 1)。現在,按照 AMT 安裝指示將 AMT22 編碼器安裝在馬達或組件上,以確保安裝正確。AMT22 支援 9 種不同的軸尺寸,範圍從 2 mm 至 8 mm。

Same Sky 的 AMT22 編碼器切換到 RUN 模式示意圖圖 1:將 AMT22 編碼器背面的開關撥到 RUN 模式。(圖片來源:Same Sky)

圖 2 和表 1 中所述的連接,專用於 Arduino Uno 板,但提供的程式碼應可相容於絕大多數的 Arduino 板。但請記住,引腳配置可能因各種 Arduino 型號而異。如需其他板卡的精確連接詳情,建議參考對應的 Arduino 說明文件。

Arduino Uno 與 AMT22 編碼器的接線連接圖圖 2:Arduino Uno 與 AMT22 編碼器的接線連接。(圖片來源:Same Sky)

功能 編碼器引腳編號 Arduino Uno 引腳 AMT-DBC-1-036
+5 V 1 5 V 白/綠
SCLK 2 13 藍/白
MOSI 3 11 白/藍
GND 4 GND 綠/白
MISO 5 12 橘/白
CS 6 2 白/橘

表 1:Arduino Uno 的詳細接線連接。(圖片來源:Same Sky)

當 SPI 通訊開始時,AMT22 編碼器會立即開始傳輸絕對位置的資料,無需傳統的命令回應結構。在 SPI 傳輸第一個位元組期間,主機會傳送 0x00,AMT22 會同時用有效的位置資料進行回應。

如果主機需要發出命令 (表 2),例如歸零命令,則會傳輸第二個位元組時傳送。這稱為擴充命令。若要瞭解技術細節,請參閱 AMT22 規格書

命令 位元組 備註
取得位置 0x00 0x00
歸零 0x00 0x70 限單匝
取得匝數 0x00 0xA0 限多匝

表 2:定義 AMT22 命令。(圖片來源:Same Sky)

程式碼教學 – 涵蓋與定義

由於 Arduino 的 SPI 匯流排用於介接 AMT22 編碼器,因此 SPI 函式庫必須納入程式碼中。為了將位置資料從 Arduino 傳送到電腦,會利用 Arduino IDE 裡的內建 USB 序列連接,並將鮑率配置為 115200。

此外,也要定義 AMT22 所用的命令。由於編碼器並不會處理第一個位元組的內容,因此會分配一個 NOP (無操作) 來簡化通訊流程 (清單 1)。

複製
/* 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

清單 1:設置 SPI 介面。

初始化

在 setup() 函數 (清單 2) 中,首先要初始化所有必要的 SPI 引腳,並配置通訊用的序列介面。

應初始化序列埠以便將資料傳輸到主機電腦。作法是將已定義的 BAUDRATE 串送到 Serial.begin() 函數中。

在啟用 SPI 前,要確保晶片選擇 (CS) 行已設定為適當的狀態,以便編碼器做好通訊準備。

選擇 SPI 匯流排與 AMT22 進行通訊的時脈率。若是要進行原型製造,則適合 500 kHz 時脈率,儘管 AMT22 可支援高達 2 MHz 的速率。可透過 SPI_CLOCK_DIV32 設定來達到 500 kHz。由於 Arduino Uno 採用 16 MHz 時脈,此除式可產生 500 kHz 的 SPI 時脈率。若要進一步瞭解 SPI 時脈配置,請參閱 Arduino 說明文件。

完成所有配置後,就可使用 SPI.begin() 來初始化 SPI 匯流排,且會設置三個專用的 SPI 引腳:MISO、MOSI 和 SCLK,以便系統備妥與編碼器進行通訊。

複製
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();
}

清單 2:setup() 函數可將所有 SPI 引腳初始化。

SPI 通訊

與 AMT22 的 SPI 通訊會透過 Arduino 的 SPI 函式庫處理,而晶片選擇 (CS) 控制則會利用數位 I/O 引腳,以程式碼進行管理。digitalWrite() 函數可用於宣告或解除宣告 CS 行 (清單 3)。

AMT22 會期待有兩個位元組的 0x00 送出,且會再收到這些位元組後立即回傳資料。由於回應迅速,因此有些最低計時要求必須遵守,如 AMT22 規格書所述。

無論編碼器是 12 還是 14 位元款式,始終會用兩個位元組 (16 位元) 的資料進行回應。高位的兩個位元是核對位元,用於驗證資料完整性。若是 12 位元款式,低位的兩個位元都會是 0,並且返回值必須向右移動 2 位元 (或除以 4) 才能正確使用。

若要取得位置資料,需調用 SPI.transfer() 函數,以傳送 AMT22_NOP 命令。在此過程中,CS 行會保持低位。AMT22 會先傳送高位位元組,因此接收到的位元組會向左移動 8位元,以便與 uint16_t 變數的上半部分對齊。此值會以單次操作分配到 encoderPosition 變數。在短暫延遲以符合計時要求後,會調用第二次 SPI.transfer(),以傳送另一道 AMT22_NOP 命令。結果會與 encoderPosition 中的當前值進行 OR 運算,以便有效地將兩個接收到的位元組合併成單一個 uint16_t 變數。最後,CS 行會解除,以完成通訊。

複製
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);

清單 3:設置 SPI 通訊。

核對和驗證

完成 SPI 傳輸後,必須使用核對和來驗證接收到的資料 (清單 4)。

要進行此驗證,可依據規格書提供的方程式建立一個函數。核對和包含在接收值的高位兩位元中,且會在位置回應中利用奇數位和偶數位的奇數奇偶校驗。

此函數將執行以下步驟:

  1. 計算奇數位 (位元 1、3、5、7、9、11、13) 的同位
  2. 計算偶數位 (位元 0、2、4、6、8、10、12、14) 的同位
  3. 將計算出的同位與核對和位元所指的值進行比較

如果核對和有效,該函數就會回傳 true,表示資料完整性已確認。如果核對和無效,該函數會回傳 false,表示接收到的資料中具有潛在錯誤。

複製
/*
 * 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);
}

清單 4:驗證核對和。

資料格式

如果核對和驗證確認了資料的完整性,下一步就是將高位兩個位元刪除,藉此更新 encoderPosition 變數 (清單 5)。作法是利用 0x3FFF (或 0b0011111111111111) 來執行全位元的 AND 運算,這可有效地保留位置資料的所有 14 個低位位元。

此外,無論是 12 或 14 位元款式,都必須將編碼器的解析度納入考量。如果解析度為 12 位元,則必須將 encoderPosition 值向右移動 2 位元,以調整較低的解析度。這可確保位置資料在 encoderPosition 變數中準確呈現,進而依據編碼器的指定解析度反映編碼器的實際位置。

複製
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");
}

清單 5:更新 encoderPosition。

設定零位 (僅限單匝)

AMT22 編碼器的某些型號有提供可編程零位功能。若要設定零位,必須傳送特定的雙位元組命令序列。此過程包括先傳送 AMT22_NOP 命令,接著短暫等待以符合 AMT22 指定的最低計時要求。在等待後,會傳送 AMT22_ZERO 命令,同時確保晶片選擇 (CS) 行解除。編碼器收到此命令後,就會執行複歸操作 (清單 6)。

為了避免在複歸期間與編碼器通訊,會實施一段 250 ms 的延遲,確保在啟動期間不會向編碼器傳送任何命令。

雖然程式碼可以在運算開始時就設置編碼器的零位,但在典型應用中,更常見的作法是在裝置的初始配置期間,僅設定一次零位,以便在系統內使用。這種作法有助於在編碼器的整個使用壽命內維持位置回饋的完整性。

複製
/*
 * 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
}

清單 6:設定單匝 AMT22 編碼器的零位。

讀取圈數計數器 (僅限多匝)

AMT22 編碼器的某些款式有支援多匝計數器,能讓使用者在單個資料檢索序列中同時讀取位置和圈數。

如果收到的位置資料無效,系統應向使用者通知此錯誤。相反地,如果位置有效,程式應以十進位格式回報位置 (清單 7)。此功能可完整回饋絕對位置和完整圈數,藉此增強編碼器的功能,對於需要精確旋轉資料的應用來說,這有利於達到更精確的監測和控制。

複製
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);

清單 7:讀取多匝 AMT22 編碼器中的 encoderPosition 和圈數計數器。

執行程式碼

順利建立程式碼後,就可以將其上傳到 Arduino 並與 AMT22 編碼器建立通訊。

若要監測輸出,請在 Arduino IDE 中開啟序列監測器,並確保數據傳輸率設定為 115200 鮑率。這可讓使用者觀察編碼器的運作,並即時查看回報的位置資料。序列監測器作用後,編碼器應該就會開始傳輸位置資訊,在系統內展現其功用 (圖 3)。

編碼器回報的位置示意圖圖 3:Arduino 接收編碼器回報的位置 (圖片來源:Same Sky)

多個編碼器

使用 SPI 裝置有個顯著的優勢在於能與同一個匯流排上的多個編碼器通訊。為了促成此優勢,需針對每個編碼器分配一個額外的數位 I/O 引腳,以達到單獨的晶片選擇 (CS) 控制。

在範例程式碼中 (清單 8),則利用 CS 引腳陣列來支援任意數量的編碼器。此設計能達到可擴充的通訊,能讓使用者根據需求輕鬆添加更多編碼器。只要修改函數,接受對應所需裝置的引腳數量,程式碼就可動態控制要在 SPI 匯流排上啟動的編碼器,藉此確保每個裝置都可以單獨存取和操作。

複製
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

清單 8:設置用於讀取多個編碼器的陣列。

下一步是輪詢陣列中的各個 CS 引腳,並從每個連接的編碼器讀取位置。這可讓系統宣告其晶片選擇行、執行 SPI 傳輸和取得位置資料,藉此啟動每個編碼器。此程式碼會按順序選擇各個編碼器、執行 SPI 通訊,並解除 CS 行,確保查詢所有連接的裝置以取得其位置資訊 (清單 9)。

複製
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);
}

清單 9:從多個編碼器讀取 encoderPosition 變數。

資料傳輸後,在解除晶片選擇行之前需要一段最小的等待時間。根據規格書,此最小時間為 3 微秒。雖然此延遲通常會在較慢的數據傳輸率下自然觀察到,但最好在程式碼中刻意實作,以確保達到正確操作並遵守計時規定。如此便可確保與 AMT22 編碼器達到可靠通訊。

結論

使用者現在應該對配置 Same Sky 的 AMT22 絕對編碼器和讀取資料有基本的瞭解。本文的重點是 AMT22 絕對編碼器。Same Sky 還擁有一系列 AMT 模組化編碼器,可提供多種增量、絕對和換向款式。

聲明:各作者及/或論壇參與者於本網站所發表之意見、理念和觀點,概不反映 DigiKey 的意見、理念和觀點,亦非 DigiKey 的正式原則。

關於作者

Damon Tarry, Design Applications Engineer, Same Sky