AN-2619:TMC8100支持Tamagawa 绝对式编码器协议

描述

TMC8100 内置一款可编程微控制器,针对速率高达 的串行同步及异步绝对式编码器协议进行了优化。它可替代专用的编码器协议接口芯片和 实施方案,同时支持通过系统内更新来适配不同编码器的功能,或切换至其他编码器协议。 是一款紧凑、经济且灵活的通信解决方案,能为工业驱动器增添绝对式编码器支持功能。

本应用笔记详细介绍了 支持 绝对式编码器的软件参考实现方案。

产品特性

  • 产品特性实现方案
  • 支持绝对式编码器
  • 支持数据读取及动态循环冗余校验 校验和计算

应用

  • 伺服驱动器位置反馈
  • 伺服驱动器电机控制
图1. TMC8100与 Tamagawa 绝对式编码器的连接

系统描述

伺服电机驱动器(例如在工业应用中)通常需要精准、可靠且低延迟的位置反馈。长期以来,带有增量式A/B/N输出的光学编码器一直是行业标准。尽管如此,绝对式位置编码器正逐渐普及,这类编码器往往具备额外功能和不同的接口协议(多为厂商专有协议)。例如, 的 协议便是用于编码器与控制器之间数字数据串行传输的一种协议。 协议的物理层基于 标准,可传输位置值、诊断信息,并支持读写编码器内部存储器。

采用 协议的编码器通过单屏蔽四芯电缆与 评估板连接(图 )。四芯电缆的构 成如下:

  • +5V 和GND :编码器电源和接地连接
  • DATA+和DATA- :用于数据通信的差分 信号(双向)

参考实现方案的特性包括:

  • T-Format协议支持的2.5Mbps数据传输速率
  • 发送命令(ID0、ID1、ID2、ID3、ID6、ID7、ID8、IDC、IDD)并接收回复
  • 数据的打包和解包
  • 动态计算CRC校验和,并添加至传输数据中
  • 动态计算CRC校验和,并与接收的校验和进行比对

参考实现方案以源代码形式提供。用户可在此基础上,根据应用需求进行修改。

系统概述

所提供的软件专为配合 使用而设计,并已通过Tamagawa 的 TS5667n120 编码器进行测试。

图2. 核心硬件元件和连接

TMC8100-EVAL-KIT 应用笔记所使用的核心硬件元件包括TMC8100 芯片和RS485 收发器,后者用于在TMC8100 与连接 器处的差分RS485 信号(即DATA+ 和DATA- )之间进行转换(图 )。

软件包含 TMC8100 的固件,实现了支持协议所需的控制器功能。此固件需在设备上电后下载至TMC8100 中。此外,还提供一款基于 Python 脚本的图形用户界面(GUI) ,可用于选择和下载固件,并且之后还可用于对固件功能进行演示和测试。

Tamagawa T-Format协议

Tamagawa 是一家专注于编码器技术的制造商,所生产的编码器提供增量式或绝对式位置输出。本参考实现方案聚焦于采用RS485 线路驱动器、以数字输出形式提供绝对位置信息的编码器。所采用的通信协议格式称为T-Format 。

使用 T-Format 与编码器进行通信时,通常的流程是:控制器先向编码器发送请求,编码器返回回复后,控制器方可发送下一个请求。

T-Format 通信协议大致可分为三类请求:数据读取、复位、内置 EEPROM的访问。不同的请求通过唯一的 ID代码进行区分。

表1. 参考实现方案支持的 代码
事务类型 ID CODE DESCRIPTION
数据读取 0 单圈绝对数据(ABS)
1 多圈数据(ABM)
2 编码器 ID (ENID)
3 ABS + ABM + ENID + 编码器错误状态 (ALMC)
EEPROM 写入 6 写入EEPROM 地址 (ADF)和数据(EDF)]
EEPROM 读取 D 读取 EEPROM [地址 (ADF)]
复位 7 复位ABS 值
8 复位ABM 值
C 复位错误

数据读取时, TMC8100向编码器发送一个控制字段 (CF)数据报(表2 ),编码器则返回包含该CF 副本、状态字段(SF) 、若干与请求对应的数据包及CRC 校验和(CRC) 的回复(表3 )。在EEPROM 写入操作中,TMC8100 发送的内容包括CF ,其后紧跟 EEPROM 地址字段(ADF) 、 EEPROM 数据字段(EDF) 及CRC 。 EEPROM读取操作包含控制字段(CF) 、 EEPROM地址字段 (ADF)及 CRC。上述两种操作中,编码器的回复均包含EEPROM 地址字段 (ADF)、 EEPROM数据字段 (EDF)及CRC 校验和。复位操作时, TMC8100发送 CF,而编码器的回复中始终包含单圈绝对位置值(ABS) 。

表2. 编码器请求格式
ID 代码 事务类型 TMC8100 传输的字段
0 数据读取 CF
1 数据读取 CF
2 数据读取 CF
3 数据读取 CF
6 EEPROM 写入 CF ADF EDF CRC
D EEPROM 读取 CF ADF CRC
7 复位 CF
8 复位 CF
C 复位 CF

CF: 控制字段
ADF: EEPROM 地址字段
EDF: EEPROM 数据字段
CRC: CRC字段

表3. 编码器回复格式
ID 代码 事务类型 传输的字段(编码器)
0 数据读取 CF SF ABS0 ABS1 ABS2 CRC
1 数据读取 CF SF ABM0 ABM1 ABM2 CRC
2 数据读取 CF SF ENID CRC
3 数据读取 CF SF ABS0 ABS1 ABS2 ENID ABM0 ABM1 ABM2 ALMC CRC
6 EEPROM 写入 CF ADF EDF CRC
D EEPORM读取 CF ADF EDF CRC
7 复位 CF SF ABS0 ABS1 ABS2 CRC
8 复位 CF SF ABS0 ABS1 ABS2 CRC
C 复位 CF SF ABS0 ABS1 ABS2 CRC

ABS: 单圈绝对位置
ABM: 多圈位置
ENID: 编码器 ID
ALMC: 编码器错误

例如,图3 展示了读取编码器单圈绝对位置( 代码 )的请求与回复格式。

图3 数据读取的帧格式

通信采用异步方式。每个字段包含10位: 1个起始位、 8位数据( LSB在前)及 1个停止位。

控制字段(CF)包含一个起始位、三个同步位、ID代码(本例中为ID代码0)及一个停止位(图4)。

图4.控制字段(CF)格式

编码器的绝对位置值被拆分为连续的三个字节(ABS0至ABS2),且按照最低有效字节优先的顺序传输(图5)。

图5.数据字段(ABS0、ABS1和ABS2)格式

最后一个数据字段后传输的CRC校验码,用于覆盖并保护控制字段(CF)、状态字段(SF)及数据字段(ABS0、ABS1和ABS2)中的8位数据(不含起始位和停止位)(图6)。

图6.CRC校验码格式

本节旨在简要概述T-Format协议。有关更多详细信息,请参见Tamagawa提供的技术规格文档。

软件概述

TMC8100的Tamagawa协议固件以两种形式提供:源代码“tmc8100-eval_tamagawa_encoder_demo_v10.asm”和英特尔十六进制文件格式的机器码代码“tmc8100-eval_tamagawa_encoder_demo_v10.hex”。为便于功能测试与评估,还提供了Python脚本“tmc8100-eval_tamagawa_encoder_demo_v10.py”。此脚本需在通过USB连接TMC8100-EVAL-KIT的PC上运行,编码器连接方式如图1所示。

运行该Python脚本程序需要在PC上预装Python解释器,并安装“intelhex”和“pySerial”等Python库,它使用“tkinter”作为图形用户界面。

将编码器连接到TMC8100-EVAL-KIT、通过USB将评估板连接至PC,并为TMC8100-EVAL-KIT施加+5V电压后,即可从命令行执行该Python脚本:

首先,需选择用于与TMC8100-EVAL-KIT建立USB连接的虚拟COM端口(本示例中为“COM4”)。终端窗口的输出内容会显示已成功连接到Landungsbruecke (LB),并检测到TMC8100,同时显示芯片ID和版本号。

随后,图形用户界面(GUI)会在独立窗口中自动启动。

首先,需在“Select Input File”(选择输入文件)区域(1)点击“…”按钮,选择包含TMC8100示例代码的十六进制文件“tmc8100-eval_tamagawa_encoder_demo_v10.hex”。随后点击“Load + Execute”(加载+执行)按钮(2),文件内容将通过USB和Landungsbruecke/LB接口,借助引导加载程序写入TMC8100的SRAM程序存储器,并启动程序执行。请注意,此程序使用专有的通信协议访问编码器。如需再次将TMC8100切换至引导加载模式(以便下载相同或不同的程序等),需进行复位或断电重启。

窗口中部的Tamagawa T-Format区域左侧设有独立按钮,对应不同的命令(ID0至IDD)。按下ID0按钮会发送相应命令,并从编码器读取单圈绝对位置(显示在“ABS”标签旁)和状态标志(显示在“SF”标签旁)。编码器发送的CRC校验和,以及TMC8100接收串行数据时同步计算的校验和,分别显示在“CRC calculated”(计算的CRC)和“CRC received”(接收的CRC)标签旁。

除了图形用户界面(GUI)中显示的已提取数据/相关数据外,命令行窗口还会显示原始通信数据及一些附加信息,对于修改或扩展TMC8100程序示例可能有所帮助。

固件实现

示例源代码“tmc8100-eval_tamagawa_encoder_demo_v10.asm”可用作起点,并可根据应用需求进行修改。汇编器(Assembler)可用于对源代码进行编译转换。

流程图(图7)概述了示例代码的整体结构,重点展示ID代码0命令的传输和回复接收过程。

固件源代码开头定义了一些值(例如支持的软件版本和协议),并列出了TMC8100内部外设单元的寄存器地址,以此提升代码的可读性。

SOFTWARE_VERSION_MAJOR = $01
 SOFTWARE_VERSION_MINOR = $00
 PROTOCOL_3 = $54 ; "T"
 PROTOCOL_2 = $41 ; "A"
 PROTOCOL_1 = $4D ; "M"
 PROTOCOL_0 = $54 ; "T"
 . . . .
 ; spi register address
 SPI_BUFFER_0 = $30

图7.固件实现概述

时钟选择与初始化

TMC8100始终以内置振荡器启动,上电/复位后,引导程序会将锁相环(PLL)配置为75MHz的系统时钟频率。对于Tamagawa协议,需要更精确的时钟源。本例中,使用的是TMC8100-EVAL-KIT上配备的16MHz晶体振荡器,当然也可选择外部时钟作为替代方案。为简化后续的时钟计算或分频器设置,将PLL输出及系统频率设定为100MHz。

第一步,将GPIO0和GPIO1引脚配置为外部晶体与内置晶体振荡器配合使用的模式。

LDI $03, r0 ; enable input for GPIO0/GPIO1
 ST GPIO_IN, r0

 LDI $03, r0 ; disable pull-up for GPIO0/GPIO1
 ST GPIO_PU, r0

下一步,将PLL反馈分频器配置为100MHz的PLL输出频率(PLL_FB_100),并为晶体振荡器(XTAL)配置时钟电路,同时设置PLL输入分频器,使PLL输入时钟频率达到1MHz。由于时钟模块的每个寄存器需通过间接方式访问,写入操作需要执行四个命令,先通过一对加载(LDI)和存储(ST)指令设置寄存器地址,再通过另一对加载和存储指令设置新的寄存器值。

LDI PLL_FB_CFG, r0 ; set pll feedback divider
 ST CLK_ADDR, r0
 LDI PLL_FB_100, r0
 ST CLK_DOUT, r0 ; will trigger write access to clk register

 LDI CLK_CTRL_SOURCE, r0
 ST CLK_ADDR, r0
 LDI $26, r0 ; use XTAL
 ST CLK_DOUT, r0 ; will trigger write access to clk register

 LDI CLK_CTRL_OPT, r0 ; enable clk fsm
 ST CLK_ADDR, r0
 LDI $40, r0
 ST CLK_DOUT, r0 ; will trigger write access to clk register

 LDI CLK_CTRL_PLL_CFG, r0
 ST CLK_ADDR, r0
 LDI %1011_1101, r0 ; RDIV = 15 (assuming 16MHz external / XTAL clock)
 ; and select PLL output, start FSM (commit = 1)
 ST CLK_DOUT, r0 ; will trigger write access to clk register

最后一次写入操作还会触发时钟模块的内部状态机,以应用所有更改。由于这一过程包括晶体振荡器的启动和PLL的锁定,因此需要检查时钟模块的状态寄存器,等待100MHz的新系统时钟就绪。为此,下一步需选定时钟模块配置寄存器(CLK_CTRL_PLL_CFG)的地址,程序会进入一个循环,不断读取该寄存器,直到第7位(TEST1 $7, r0)被清零,才会继续执行后续程序。

LDI CLK_CTRL_PLL_CFG, r0
 ST CLK_ADDR, r0
 NOP
 NOP
WAIT_FOR_PLL:
 LD CLK_DIN, r0
 NOP
 TEST1 $7, r0
 JC WAIT_FOR_PLL

DIRECT_IN/DIRECT_OUT引脚配置

如图2所示,TMC8100-EVAL-KIT上仅使用一个RS485收发器与编码器进行通信。该RS485收发器的控制方式如下:通过DIRECT_OUT(1)输出串行发送数据,通过DIRECT_OUT(3)实现发送器使能,通过DIRECT_IN(1)输入串行数据(此输入信号无需配置)。

; set TXD / DIRECT_OUT(1) = 1
 SFSET WAIT1SF NO_WAIT, 0, 1
 ; set TXD_EN / DIRECT_OUT(3) = 0
 SFCLR WAIT1SF NO_WAIT, 0, 3
 ; configure TXD_EN / DIRECT_OUT(3) as output
 LDI $00, r0
 ST DIRECT_ALT_FUNCTION, r0

SPI命令循环

配置好TMC8100后,程序会进入一个无限循环,等待通过SPI接收命令。所有SPI事务均采用32位数据报形式。本例代码中的所有命令均可容纳在一个数据报内。为简化操作,仅通过检测高8位(MSB,最先通过SPI接收)来选择和执行命令。命令循环的流程如下:首先读取SPI外设模块的状态寄存器(SPI_STATUS),并等待该状态寄存器的第0位变为1(WAIT1 $0, r0),1表示已接收到SPI数据报,且数据已存入SPI输入缓冲区(SPI_BUFFER)。

CMD_LOOP:
 ; wait for SPI command
 LDI SPI_STATUS, r0
 WAIT1 $0, r0
 LD SPI_BUFFER_3, r0
 ; ID0 - read encoder absolute data in one revolution
 LDI $80, r1
 COMP EQ r0, r1
 JC tamagawa_id0
 ; ID1 - read encoder multi-turn data
 . . . .
JA CMD_LOOP

将32位SPI输入缓冲区内容的高8位与$80进行比对,$80是本例代码中定义的、用于向编码器发送ID0请求(以读取单圈绝对位置(ABS))的SPI命令。如果比对结果匹配,程序会跳转到“tamagawa_id0”地址执行。该地址处的程序代码(后续章节将详细阐述)会通过DIRECT_OUT(1)发送ID0命令数据报,并通过DIRECT_IN(1)接收编码器的回复。接收到的数据会被组装成32位SPI数据报,并存入SPI输出缓冲区。与此同时,SPI_BUFFER_FULL/GPIO会从低电平变为高电平,以此指示有新数据可供SPI事务处理。在后续的SPI事务中,这些数据可被读取。在读取完前一条命令的所有数据之前,不应发送新命令。由于一次SPI事务始终在双向传输数据,且通常需要多次事务才能读取完所有回复数据,因此建议对除了最后一次外的所有事务使用“虚拟”命令(例如0x00 0x00 0x00 0x00),命令循环不会对这类虚拟命令进行解析。最后一次读取事务中,可包含下一条命令。

用于测试编码器实现的Python脚本“tmc8100-eval_tamagawa_encoder_demo.py”(适用于TMC8100-EVAL-KIT)提供了一个带ID0按钮的图形用户界面(GUI)。按下此按钮后,Landungsbruecke(简称LB)会发送一个SPI数据报0x80 0x00 0x00 0x00(摘录自脚本“tmc8100-eval_tamagawa_encoder_demo.py”):

def spi_tamagawa_id0():
 # get ABS value
 value = SpiWriteCommand([0x80, 0x00, 0x00, 0x00])
 WaitForGPIO6()
 . . . .

随后,程序会等待编码器的回复。一旦TMC8100的SPI输出缓冲区中有回复数据可用,SPI_DATA_AVAILABLE/GPIO6引脚就会从低电平切换到高电平。为了读取这些数据,Python程序会使用SPI“虚拟”命令0x00 0x00 0x00 0x00,该命令不会被TMC8100固件解译。

# get ABS value
 value = SpiWriteCommand([0x00, 0x00, 0x00, 0x00])
 print(f"ID0: {value[0]:02x} {value[1]:02x} {value[2]:02x} {value[3]:02x}")
 . . . .
 # get CRC + SF value
 value = SpiWriteCommand([0x00, 0x00, 0x00, 0x00])
 print(f"ID0: {value[0]:02x} {value[1]:02x} {value[2]:02x} {value[3]:02x}")
 . . . .

下表概述了示例程序支持的所有SPI命令及相应的回复数据。SPI命令始终包含在一个32位数据报中,而回复最多可能占用五个32位数据报。SPI 32位数据报以四个连续的十六进制数表示,每个字节对应一个十六进制数,采用MSB在前的格式以提升可读性。对于回复,32位数据报依据被放入SPI缓冲区或可被读取的顺序显示。

SPI命令(32位 SPI回复(32位)
0x80 0x00 0x00 0x00
ID代码0请求 - 读取编码器单圈绝对位置数据
1. 0x10 ABS0 ABS1 ABS2
2. 0x70 SF 接收到的CRC 计算出的CRC
0x81 00 00 00
ID代码1请求 - 读取编码器多圈数据
1. 0x20 ABM0 ABM1 ABM2
2. 0x70 SF 接收到的CRC 计算出的CRC
0x82 00 00 00
ID代码2请求 - 读取编码器ID
1. 0x40 0x00 0x00 ENID
2. 0x70 SF 接收到的CRC 计算出的CRC
0x83 00 00 00
ID代码3请求 - 读取编码器绝对位置、多圈数据、ID和错误标志
1. 0x10 ABS0 ABS1 ABS2
2. 0x40 0x00 0x00 ENID
3. 0x20 ABM0 ABM1 ABM2
4. 0x50 0x00 0x00 ALMC
5. 0x70 SF 接收到的CRC 计算出的CRC
0x86 00 EDF ADF
ID代码6请求 - 向7位地址写入8位数据
1. 0x50 0x00 EDF ADF
2. 0x70 SF 接收到的CRC 计算出的CRC
0x87 00 00 00
ID代码7请求 - 复位错误标志
1. 0x10 ABS0 ABS1 ABS2
2. 0x70 SF 接收到的CRC 计算出的CRC
0x88 00 00 00
ID代码8请求 - 复位编码器绝对位置计数器
1. 0x10 ABS0 ABS1 ABS2
2. 0x70 SF 接收到的CRC 计算出的CRC
0x8c 00 00 00
ID代码C请求 - 复位编码器多圈计数器
1. 0x10 ABS0 ABS1 ABS2
2. 0x70 SF 接收到的CRC 计算出的CRC
0x8d 00 00 ADF
ID代码D请求 - 从7位地址读取8位数据
1. 0x50 0x00 EDF ADF
2. 0x70 SF 接收到的CRC 计算出的CRC
0xff 00 00 00
获取固件版本
1. 0xff 0x00 主版本号 次版本号
0xfe 00 00 00
获取编码器协议
1. 0x54 (“T”) 0x41 (“A”) 0x4d (“M”) 0x54 (“T”)

ID代码0请求

当TMC8100软件处于等待SPI命令的状态时,一旦接收到新的SPI命令,且该32位数据报的最高字节(即最先接收到的字节)等于$80,程序执行将跳至代码中的“tamagawa_id0”地址,并开始通过DIRECT_OUT(1)和连接的RS485收发器向编码器准备并发送ID代码0请求。

; set timer to 100MHz / 40 = 2.5MHz
 LDI 39, r0
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_LIMIT_W
 LDI 2, r0 ; enable timer
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_CTRL_W
 ; switch on driver
 SFSET WAIT1SF NO_WAIT, 0, 3
 . . . .

对于编码器协议,所需的传输数据速率为2.5Mbit/s。由于内部系统时钟设为100MHz,用于位移出操作的系统定时器被设为39,以获得100/2.5=40的分频系数。随后,定时器启用,并通过设置DIRECT_OUT(3)输出,开启外部RS485驱动器。

 ; start bit + sync code
 LDI %0000_0100, r0
 ; id0 + parity
 LDI %0000_0000, r1
 REP 4, 1
 SHRO WAIT1SF WAIT_OVERFLOW_TIMER, r0, FLAG_OUT1 ; shift out start bit + sync code
 REP 5, 1
 SHRO WAIT1SF WAIT_OVERFLOW_TIMER, r1, FLAG_OUT1 ; shift out ID0 + parity
 WAIT1SF WAIT_OVERFLOW_TIMER, WAIT_NO_ACTION ; wait time for last bit before stop bit
 ; stop bit
 SFSET WAIT1SF NO_WAIT, 0, 1
 . . . .
 WAIT1SF WAIT_OVERFLOW_TIMER, WAIT_STOP_TIMER
 ; end of command - switch off driver
 SFCLR WAIT1SF NO_WAIT, 0, 3

在等待一些时间,以便RS485收发器输出稳定后,通过SHRO指令将起始位(“0”)和3位同步码(“010”)从系统寄存器r0经DIRECT_OUT(1)移出。该指令使用REP 4, 1执行四次。每次执行时,程序会等待系统定时器溢出(参数WAIT_OVERLFLOW_TIMER),然后从r0寄存器移出LSB。随后,采用类似方式移出包含ID代码0和奇偶校验位的数据报的剩余5位,这次使用系统寄存器r1,并通过REP 5, 1重复执行SHRO指令五次。DIRECT_OUT(1)串行输出随后将停止位切换为“1”,最后将DIRECT_OUT(3)置为低电平,以关闭RS485收发器并准备接收来自编码器的数据。

图8.ID0发送序列

ID代码0回复

为准备接收回复,需初始化CRC计算模块,以便在串行数据移入过程中实时进行CRC计算。

 ; initialize CRC for 8bit CRC
 LDI $0, r0
 STS r0, SYSTEM_CRC, SYSTEM_CRC_CTRL_W ; reset CRC block
 LDI %0000_0001, r0 ; CRC polynomial: x^8 + x^0
 STS r0, SYSTEM_CRC, SYSTEM_CRC_POLYNOM_W ; LSB
 LDI %0000_0001, r0 ; CRC polynomial: x^8 + x^0
 STS r0, SYSTEM_CRC, SYSTEM_CRC_POLYNOM_W
 LDI %0000_0000, r0
 STS r0, SYSTEM_CRC, SYSTEM_CRC_POLYNOM_W
 STS r0, SYSTEM_CRC, SYSTEM_CRC_POLYNOM_W ; MSB

首先,复位用于CRC计算的线性反馈移位寄存器(LFSR),并将该协议中用于计算CRC的多项式加载到系统CRC寄存器(SYSTEM_CRC_POLYNOM_W)中。该寄存器大小为32位,在CRC模块复位后,通过四次字节宽度的写入操作向同一寄存器填充数据,且采用LSB优先的方式。需要设置第0位和第8位,以配置编码器协议所使用的生成多项式(x^8 + x^0)。

随后,程序开始准备接收编码器发来的第一个数据报(控制字段 CF)。

 ; reset timer and set timer limit to middle of bit
 LDI 18, r0
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_LIMIT_W
 ; wait for falling edge (start bit)
 WAIT0SF WAIT_IN1, WAIT_START_TIMER
 ; wait for middle of start bit
 WAIT1SF WAIT_OVERFLOW_TIMER, WAIT_NO_ACTION
 ; set limit to 100MHz / 39 = 2.5MHz and no timer reset
 LDI 39, r0
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_LIMIT_NO_RESET_W
 REP 8, 1
 ; wait for timer overflow and shift in sync code 010 and cc0..cc4
 SHRI WAIT1SF WAIT_OVERFLOW_TIMER, FLAG_IN1_CRC, r1 ; CF -> r1
 ; wait for timer overflow, stop bit
 WAIT1SF WAIT_OVERFLOW_TIMER, WAIT_STOP_TIMER

配置系统定时器后,程序进入等待状态(WAIT0SF),监测DIRECT_IN(1)上编码器回复消息起始位的下降沿,一旦检测到便立即启动定时器(WAIT_START_TIMER)。定时器已预先设置为大约在前一次溢出时间的一半时溢出(将限值设为18,而非39),以此到达起始位的中间位置。一旦到达该位置,限值会重新设为39,以便在每个输入数据位的中间位置触发一次定时器溢出事件(图9)。接下来,通过重复命令REP 8, 1配合右移输入命令SHRI,将后续8位输入数据以LSB优先的方式移入系统寄存器r1,同时将这些数据传入CRC单元进行CRC计算。当到达最终停止位的中间位置时,定时器停止。此时,接收的控制字段数据已存入寄存器r1中,且针对接收的前8位数据计算得到的CRC校验和也已生成。

图9.ID0回复控制字段接收序列

编码器回复的第二个数据报包含状态字段(SF),对应预期接收的第二个字节。再次将定时器溢出时间设为起始位的中间位置,一旦在DIRECT_IN(1)上检测到下降沿,立即启动定时器。

; reset timer and set timer limit to middle of bit
 LDI 18, r0
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_LIMIT_W
 ; wait for falling edge (start bit)
 WAIT0SF WAIT_IN1, WAIT_START_TIMER
 ; wait for middle of start b
 WAIT1SF WAIT_OVERFLOW_TIMER, WAIT_NO_ACTION
 ; set limit to 100MHz / 39 = 2.5MHz and no timer reset
 LDI 39, r0
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_LIMIT_NO_RESET_W
 REP 8, 1
 ; wait for timer overflow and shift in dd0..dd3, ea0..ea1, ca0..ca1
 SHRI WAIT1SF WAIT_OVERFLOW_TIMER, FLAG_IN1_CRC, r2 ; SF -> r2
 ; wait for timer overflow, stop bit
 WAIT1SF WAIT_OVERFLOW_TIMER, WAIT_STOP_TIMER

通过REP指令配合SHRI指令,将8位数据移入寄存器r2,同时传入CRC单元。当到达停止位的中间位置时,定时器停止。最终,接收的状态字段存入寄存器r2中,此时CRC校验和已基于控制字段的8位数据与状态字段的8位数据完成计算。

接下来,预期接收的是单圈内24位绝对编码器位置值,该值分为三个数据报,每个数据报包含一个字节,且采用LSB优先的顺序(分别为ABS0、ABS1、ABS2)。这段程序代码的结构与此前处理状态字段数据报的代码极为相似。

; reset timer and set timer limit to middle of bit
 LDI 18, r0
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_LIMIT_W
 ; wait for falling edge (start bit)
 WAIT0SF WAIT_IN1, WAIT_START_TIMER
 ; wait for middle of start bit
 WAIT1SF WAIT_OVERFLOW_TIMER, WAIT_NO_ACTION
 ; set limit to 100MHz / 39 = 2.5MHz and no timer reset
 LDI 39, r0
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_LIMIT_NO_RESET_W
 REP 8, 1
 ; wait for timer overflow and shift in d0..d7
 SHRI WAIT1SF WAIT_OVERFLOW_TIMER, FLAG_IN1_CRC, r3 ; ABS0 -> r3
 ; wait for timer overflow, stop bit
 WAIT1SF WAIT_OVERFLOW_TIMER, WAIT_STOP_TIMER

编码器位置信息依次存储在系统寄存器r3(ABS0)、r4(ABS1)以及最后的r5(ABS2)中。CRC校验和的计算会持续进行,将这三个字节也纳入校验范围。

作为ID代码0请求的编码器回复的最后一个数据报,系统将接收CRC校验和。该数据报的处理代码与前几个数据报的处理代码结构基本相同。

 ; reset timer and set timer limit to middle of bit
 LDI 18, r0
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_LIMIT_W
 ; wait for falling edge (start bit)
 WAIT0SF WAIT_IN1, WAIT_START_TIMER
 ; wait for middle of start bit
 WAIT1SF WAIT_OVERFLOW_TIMER, WAIT_NO_ACTION
 ; set limit to 100MHz / 39 = 2.5MHz and no timer reset
 LDI 39, r0
 STS r0, SYSTEM_TIMER, SYSTEM_TIMER_LIMIT_NO_RESET_W
 REP 8, 1
 ; wait for timer overflow and shift in rc0..rc7
 SHRI WAIT1SF WAIT_OVERFLOW_TIMER, FLAG_IN1, r6 ; CRC -> r6
 ; wait for timer overflow, stop bit
 WAIT1SF WAIT_OVERFLOW_TIMER, WAIT_STOP_TIMER

请注意,此次通过SHRI指令移入寄存器的位不会同时并行移入CRC单元(移位命令SHRI的最后一个参数使用FLAG_IN1而非FLAG_IN1_CRC)。随后,接收到的CRC值将存储在系统寄存器r6中。

至此,所有接收到的数据均已存储在系统寄存器r1至r6中。下一步,这些值将被复制到SPI缓冲区,以供连接的微控制器/运动控制器读取。

 ; copy reply to SPI transmit buffer
 LDI %0001_0000, r0 ; its the ABS value
 ST SPI_BUFFER_3, r0 ; 0x10 -> ABS value
 ST SPI_BUFFER_2, r3 ; ABS0
 ST SPI_BUFFER_1, r4 ; ABS1
 ST SPI_BUFFER_0, r5 ; ABS2

第一个32位SPI数据报的低24位填充绝对位置信息,MSB设置为固定值0x10。当读取该SPI数据报时,可通过此固定MSB值明确识别数据报内容。

 ; copy crc value to SPI transmit buffer
 LDI %0111_0000, r0 ; crc values
 ST SPI_BUFFER_3, r0 ; 7 -> CRC + STATUS Field
 ST SPI_BUFFER_2, r2 ;
 LDS SYSTEM_CRC, SYSTEM_CRC_RESULT0_R, r0
 ST SPI_BUFFER_1, r6 ; CRC received
 REV r0, r0
 ST SPI_BUFFER_0, r0 ; CRC calculated
 JA CMD_LOOP

第二个32位SPI数据报的LSB包含CRC系统模块计算得出的CRC值。通过指令“LDS SYSTEM_CRC, SYSTEM_CRC_RESULT0_R, r0”,将计算得到的CRC值复制到系统寄存器r0中。计算得到的CRC值的位顺序与接收到的CRC值相反,因此使用指令“REV r0, r0”对寄存器位进行反转。完成之后,寄存器r0中存储的计算CRC校验和应与接收到的CRC值完全一致(前提是数据传输过程未受干扰)。

在实际应用中,需要对比计算出的CRC校验和与接收到的CRC校验和。若两者不匹配,可能意味着整个数据报/编码器信息存在异常,此时可选择丢弃/忽略该信息(例如设置错误标志)。

在本示例代码中,32位SPI数据报的LSB填充计算出的CRC值,其相邻的高字节填充接收到的CRC码,再高一位的字节则复制了接收到的状态字段位(SF)。最后,将数据报的MSB设置为固定值0x70,以此标识该数据报的内容,之后再将其复制到SPI输出缓冲区中。

附录

汇编器

为便于TMC8100的程序开发和示例/参考程序的修改,提供了一个汇编器供使用。这是一个基于PC的命令行工具,需要以汇编源代码文件的文件名和可选路径作为参数。如果未找到指定的文件名,系统会自动在提供的文件名后添加*.asm后缀。汇编器将根据这个输入文件,生成相应的输出文件。

在本示例中,汇编源代码输入文件的名称为“tmc8100-eval_abn_demo.asm”。从该文件生成的机器指令(16位)数量为83条,占可用程序存储(SRAM)的4.1%。TMC8100的程序存储器容量为2K x 16位,最多可存储2048条指令。

汇编器默认生成的输出文件与输入文件同名,仅将扩展名由 *.asm 改为 *.hex。该输出文件是一个文本文件,其中的程序代码采用标准的英特尔十六进制文件格式,例如可用于通过TMCL-IDE或示例代码中提供的某个Python脚本,将程序代码加载到TMC8100-EVAL-KIT上TMC8100的程序存储器中。

此外,该汇编器支持通过设置一系列标志参数,生成不同格式的附加输出文件及额外的辅助信息。

  • 选项“-i”:生成文件“.i”作为预处理器的输出。该文件包含汇编源文件内容,其中所有预处理命令(# ...)已被处理(例如,“#include”文件内容已合并到源文件中),且注释(例如,/* ..*/ | // | ;)已被移除。
  • 选项“-l”:生成日志文件“.log”,其包含汇编源代码(不含注释和预处理命令),并附加指令编码及各指令的程序存储器地址。
  • 选项“-c”:生成C代码文件“<filename.c>”,用于支持引导TMC8100所用控制器的程序开发。该文件包含一个整数数组,用于存储生成的机器码。 </filename.c>
  • 选项“-rom”:生成3个附加文件;其中,“pelican_bootloader.hex”和“pelican_bootloader.bin”以十六进制/二进制数形式列出机器指令,每行一个数值,而“pelican_bootloader.vhd”则将机器指令存为逻辑代码,作为VHDL实体声明的一部分。
  • 选项“-m”:机器标志,用于支持不同的实现/指令集(TM02适用于TMC8100)。
  • 选项“-h”:打印帮助测试,如屏幕截图所示。

支持的语法/命令

该汇编器目前支持TMC8100数据手册中所述的所有TMC8100指令。

预处理器命令

注释

预处理器会从输入文件中移除所有注释,以便进行后续处理。目前支持如下选项:

  • /* */——块注释,可以包含多行内容(C语言风格)
  • // ——行尾注释,从//开始至本行结束(C/C++ 语言风格)
  • ; ——行尾注释,从;开始至本行结束

#include

#include "<filename>"——将中的名称和可选路径所对应的文件的内容,插入到该#include预处理器指令所在位置,以便进行后续处理。包含#include指令的行将被移除。

注:两侧仅允许使用引号,且该行中不应包含其他命令/赋值,因为这些内容会被移除或忽略,不再参与后续处理。

#define

预处理器会将此命令之后在源文件(+通过#include语句包含的文件)中找到的任何<label>替换为 [<replacement text>]。替换文本为<label>后至少以一个空格分隔的、直至行尾(或注释起始处)的字符序列。仅注释内容和引号内文本不参与自动替换。标签可在源文件中通过另一个带有相同<label>的#define命令重新定义。

此外,允许定义无替换文本的<label> ,此时替换文本为空。这在配合条件预处理器命令(如#ifdef/#ifndef)时可能会很有用。

#ifdef, #ifndef, #else, #endif

#ifdef <label> <code block 1> #else <code block 2> #endif

若<label> 此前已被定义,则会解析<code block 1> 的内容并生成汇编输出,同时忽略<code block 2>。中间文件(“.i”)中仅显示 <code block 1> ,不显示<code block 2>。代码块可以包含多行汇编指令等内容。若<label> 尚未定义,则情况相反。

#ifndef <label> <code block 1> #else <code block 2> #endif

若 <label> 此前已被定义,则会解析<code block 2>的内容并生成汇编输出,同时忽略<code block 1>。中间文件(.i)仅显示<code block 2>,不显示<code block 1>代码块可以包含多行汇编指令等内容。若 <label> 尚未定义,则情况相反。

注:在使用#define <label> 之前,只需提及<label> 名称便已足够,无需提供替换文本/值。

#else部分是可选的。#ifdef或 #ifndef块可以嵌套使用。

汇编器命令

数字

为简化二进制、十进制和十六进制数字的表示,系统支持多种格式,具体如下:

0x123或$123解析为十六进制数(允许的字符:0-9、a-f或A-F)

%1010解析为二进制数(允许的字符:0和1)。可以插入字符“_”以增强可读性。例如,%1010_0011表示一个8位二进制数字

123解析为十进制数(允许的字符:0-9)

标识符

标识符可替代数字使用,以提高代码的可读性。标识符必须以字母(a-z或A-Z)或下划线“_”开头,后续可包含数字(0-9)。请注意,标识符名称不区分大小写。

可使用等号为标识符赋值;例如,

SPI_BUFFER_0 = $30

一些名称不应用作标识符:

  • 汇编指令,包括TMC8100数据手册中列出的所有名称,或是以“C”开头的指令名称(“C”表示该命令的条件执行)
  • 标识符r0至r7是预定义的,分别代表寄存器0至7,用于指定所有可用的通用寄存器
  • 标签与标识符不可同名

标签

标签用作程序存储器地址的占位符。标签无需赋值,在汇编器将汇编源文件转换为机器码时,会自动以当前程序存储器地址对其进行初始化。

无限循环示例:

WAIT:
JA WAIT

注意标签名称后带有“:”字符。汇编器会自动将标签WAIT初始化为下一条指令的程序存储器地址(本例中为JA WAIT命令的地址)。两种跳转方式均受支持:一是向后跳转(如上例所示),即标签在实际作为指令一部分使用之前已完成初始化;二是向前跳转,即标签在被某条指令引用之后才完成初始化。