用于 SPI 绝对编码器的 Arduino 示例代码
2024-10-08
本 Arduino 示例代码教程旨在为用户提供一个坚实的起点,以便通过串行外设接口 (SPI) 通信来配置和读取 Same Sky 的 AMT22 绝对编码器的数据。该教程将提供所需的硬件和软件、关键设置要求以及单圈和多圈输出选项的示例代码包和说明。以下是入门工作所需的物料清单:
- Arduino 板
- AMT22 编码器
- AMT-06C-1-036 电缆,或带有相应连接器的类似电缆
- Arduino IDE
- 下载 AMT22 单圈样例代码
- 下载 AMT22 多圈样例代码
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 不等。
图 1:将 AMT22 编码器背面的开关拨至 RUN 模式。(图片来源:Same Sky)
图 2 和表 1 中列出的连接专用于 Arduino Uno 板,但所提供的代码应与大多数 Arduino 板兼容。不过,请注意不同型号的 Arduino 引脚配置可能有所不同。有关其他电路板的准确连接细节,建议参考相应的 Arduino 说明文档。
图 2:Arduino Uno 与 AMT22 编码器的线路连接。(图片来源:Same Sky)
|
表 1:Arduino Uno 接线的进一步定义。(图片来源:Same Sky)
当 SPI 通信开始时,AMT22 编码器立即开始传输其绝对位置数据,无需传统的指令响应结构。在 SPI 传输的第一个字节期间,主机发送 0x00,AMT22 同时响应有效位置数据。
如果主机需要发出指令(表 2),例如置零指令,则将在传输的第二个字节中发送。这被称为扩展指令。有关详细的技术规格,请参阅 AMT22 数据表。
|
表 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 通信的时钟频率。对于原型开发而言,尽管 AMT22 支持高达 2 MHz 的时钟频率,但 500 kHz 的时钟频率还是比较合适的。通过 SPI_CLOCK_DIV32 设置可实现 500 kHz 频率。假定 Arduino Uno 的时钟频率为 16 MHz,这种分频的结果是 SPI 时钟频率为 500 kHz。有关 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:用于初始化所有 SPI 引脚的 setup() 函数。
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 变量的上半部分对齐。该值将一次性地分配给编码器位置变量。经过短暂延迟以满足时序要求后,二次调用 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、3、5、7、9、11、13 位的奇偶性
- 计算偶数位(0、2、4、6、8、10、12、14 位)的奇偶性
- 将计算出的奇偶性与校验位指示的值进行比较
如果校验和有效,该函数将返回 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 位,则编码器位置值必须向右移动 2 位,以适应较低的分辨率。这可确保位置数据在编码器位置变量中得到准确表达,即根据编码器的指定分辨率反映编码器的实际位置。
复制
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 多圈读取编码器中的编码器位置和圈数计数器。
运行代码
代码创建成功后,就可以将其上传到 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:从多个编码器读取编码器位置变量。
数据传输结束后,在释放芯片选择线之前需要极短的等待时间。根据规格书,这个极短的时间为 3 ms。虽然这种延迟通常是在较慢的数据传输速率下自然观察到的,但好的做法是在代码中明确实现这种延迟,以确保正常运行并遵守时序规范。这就确保了与 AMT22 编码器的可靠通信。
结束语
用户现在应该对配置和读取 Same Sky AMT22 绝对编码器的数据有了基本了解。本文重点介绍了 AMT22 绝对编码器。Same Sky 还拥有一系列 AMT 模块化编码器,包括增量式、绝对式和换向式版本。
免责声明:各个作者和/或论坛参与者在本网站发表的观点、看法和意见不代表 DigiKey 的观点、看法和意见,也不代表 DigiKey 官方政策。