AN-2614: TMC8100 支持SSI绝对式编码器协议

描述

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

本应用笔记详细介绍了TMC8100的软件参考实现方案,支持带有同步串行接口(SSI)的绝对位置编码器。

产品特性

  • SSI实现方案
  • 示例使用了1MHz时钟频率
  • 格雷码到二进制码的转换
  • 输入滤波器
  • SSI时钟频率最高可达16MHz

应用

  • 电机位置反馈
图1. TMC8100-EVAL-KIT 连接,显示了带有SSI的绝对位置编码器

系统描述

伺服电机驱动器(例如在工业应用中)通常需要精准、可靠且低延迟的位置反馈。长期以来,带有增量式A/B/N 输出的光学编码器一直是行业标准。尽管如此,绝对式位置编码器正逐渐普及,这类编码器往往具备额外功能和不同的接口协议(多为厂商专有协议)。例如,SSI 协议便是用于编码器与控制器之间数字数据串行传输的一种协议。它采用两条独立的连接线路:一条用于传输控制器生成的时钟信号,另一条则用于传输编码器输出的数据。其物理层基于RS422标准,可将位置值和诊断信息从编码器传输至控制器。

本示例中使用了带SSI接口的编码器,通过一根六芯电缆TMC8100-EVAL-KIT相连(em>图1)。六芯电缆的构成如下

  • +24V和GND:编码器电源和接地连接
  • DATA+和DATA-:差分RS-422信号,用于从编码器向控制器传输数据
  • CLK+和CLK-:差分RS-422信号,用于从控制器向编码器传输时钟

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

  • 生成具有所需频率、脉冲数和极性的时钟信号
  • SSI编码器支持的1Mbps数据速率
  • 数据的打包和解包
  • 将16位编码器位置数据从格雷码转换为16位二进制码

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

系统概述

所提供的软件专为配合TMC8100-EVAL-KIT使用而设计,并已通过SSI的Ri360P0-QR14-ESG25X2编码器 (Turck) 进行测试。

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

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

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

同步串行接口协议

SSI 协议采用由控制器生成的时钟信号,编码器将其作为触发与参考基准,向外输出位置数据及附加信息(如状态/错误代码)。在空闲状态下,无论是控制器发往编码器的时钟信号,还是编码器返回至控制器的数据信号,均处于高电平(逻辑‘1’)状态。

图3. SSI 数据报:编码器数据按从最高有效位(MSB)到最低有效位(LSB)的顺序进行串行传输。

当时钟信号出现第一个下降沿时,编码器数据会被传输至编码器内部的输出移位寄存器。随后,随着时钟信号的每个上升沿,数据将按最高有效位(MSB)在前、最低有效位(LSB)在后的顺序逐位移出。本示例中传输的数据为25位。其中高2位(第23、22位)包含诊断信息位,低16位则为采用格雷码编码的编码器位置信息(表 1)。数据传输完成后,数据线会先保持低电平一段固定时间,而后再切换回高电平(空闲状态)。此后,当下一个时钟线出现下降沿时,即可开始读取下一个编码器位置数据。

表1. SSI 数据报位描述
数据位 描述
第24位(MSB) 传输的第一位,未使用
第23位 定位元件超出测量范围
第22位 定位元件处于测量范围内,但信号质量较差(例如距离过远)
第21位至第16位 未使用
第15位至第0位 编码器位置信息(格雷码)

当下一个SSI数据报的时钟下降沿启动时,若数据线在传输完LSB后仍处于低电平状态,可能会重复读取 到相同的编码器位置。本示例中,由于数据报未包含校验和,这种特性可用于检查编码器数据的完整性。 尽管数据位数为25位,但实际时钟周期数为26 个,比数据位数多1个。这是因为第一个数据位是随时钟 的第一个上升沿(而非下降沿)移出的,而最后一个数据位则随倒数第二个上升沿移出。

支持SSI协议的编码器及制造商种类繁多,其数据报位数、用于编码器位置信息的位数、数据编码方式(例 如采用二进制而非格雷码)、附加信息(例如状态/错误位、多圈数据)及校验方式(例如奇偶校验/CRC) 往往存在差异。关于具体的协议实现细节,请参考编码器制造商提供的文档。

软件概述

TMC8100 的SSI协议固件以两种形式提供:源代码“tmc8100-eval_ssi_encoder_demo_v10.asm”和英特尔十 六进制文件格式的机器码代码“tmc8100-eval_ssi_encoder_demo_v10.hex”。为便于功能测试与评估,还提供了Python脚本“tmc8100-eval_ssi_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脚本:

command line 1

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

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

gui 1

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

gui 2

窗口中间的 SSI 帧区域设有两个命令按钮,用于启动编码器读取。按下“Read Encoder”(读取编码器)按钮 后,TMC8100 固件会通过SSI发送相应数量的时钟周期,并读取单圈范围内的绝对位置(显示在“ST”标签旁)。此处分别显示了两组数据:直接从编码器接收的格雷码格式数据[ST(原始/格雷码/16位)],以及转换后的16位数值[ST(转换后/二进制/16 位)]。转换过程在TMC8100固件内部完成,原始格雷码数值在此处显示仅作参考之用。

command line 2

按下“ Read Encoder continuously”(持续读取编码器)按钮后,Python程序会以固定速率触发编码器值的读取。除了显示位置值的数字外,带有红色位置标记的模拟刻度盘也会随编码器值同步更新,以此指示当 前360°绝对角度位置。若要停止编码器持续读取,必须终止或重启Python程序。

图4. 用于测试固件实现的Python GUI

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

command line 3

出于性能考虑,在持续读取编码器期间,命令行窗口的输出会被抑制。

固件实现

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

流程图 (图5)概述了示例代码。

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

    SOFTWARE_VERSION_MAJOR = $01
    SOFTWARE_VERSION_MINOR = $00
    PROTOCOL_3 = $53 ; "S"
    PROTOCOL_2 = $53 ; "S"
    PROTOCOL_1 = $49 ; "I"
    PROTOCOL_0 = $20 ; " "
    ; system register
    SYSTEM_CORE = $0
    SYSTEM_TIMER = $1
    SYSTEM_CRC = $2
图5. 固件实现概述

SPI 配置

TMC8100 自带的标准SPI信号(SPI_CSN、SPI_SCLK、SPI_SDI、SPI_SDO)无需额外配置,其功能及引脚 分配在封装中已固定。不过,另有一个 SPI_DATA_AVAILABLE 信号可替代 GPIO(6)进行配置,用于指示 TMC8100 内部固件已向SPI输出缓冲区写入数据(输出高电平“1”)。这一机制可向所连接的微控制器提供 反馈,使其可启动SPI数据报/事务,从TMC8100中读取此前发送命令的回复数据。上电后,该功能会由 引导加载程序自动配置,此处列出仅为保证内容完整性:

    LD GPIO0_ALT1_FUNCTION, r0
    LD GPIO_OUT_ENABLE, r1
    SET $4, r0, r0 ; GPIO_ALT1_FUNCTION(5 downto 4) = "01" -> connect spi_data_available to GPIO(6)
    CLR $5, r0, r0
    ST GPIO0_ALT1_FUNCTION, r0
    SET $6, r1, r1 ; GPIO(6) / SPI_DATA_AVAILABLE -> output
    ST GPIO_OUT_ENABLE, r1

时钟选择与初始化

TMC8100始终以内置振荡器启动,上电/复位后,引导程序会将锁相环(PLL)配置为75MHz的系统时钟频率。 本例中,使用的是TMC8100-EVAL-KIT上配备的16MHz晶体振荡器,为简化后续的时钟计算或分频器设置, 将PLL输出及系统频率设定为100MHz。对于SSI而言,晶振时钟并非必需,因为通信所需的参考时钟由 TMC8100 自身提供。因此,这种情况下可选用TMC8100的内部时钟搭配锁相环(PLL)作为替代方案。

第一步,将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和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收发器均用于与编码器通信。框图中上方的收发器负责将时钟信号从TMC8100传输至编码器。该RS485收发器的发送使能端始终保持开启状态。DIRECT_OUT(0)被用作时钟输出(通过命令“ST DIRECT_ALT_FUNCTION, r0”选择该输出的复用功能),而DIRECT_IN(0)则用于将RS485收 发器输出的时钟信号回环至TMC8100,以实现驱动器延迟补偿。这是一项可选功能,并非必需,因为时钟回环也可在内部完成。

框图中下方的RS485收发器用于接收来自编码器的数据。此处的发送器处于禁用状态。DIRECT_IN(1)用作串行数据输入,而DIRECT_OUT(1)和 DIRECT_OUT(3)在本应用中无需使用,故设置为固定电平(均为低电 平“0”)。

由于时钟线(CLK+)在空闲状态下为高电平“1”,对应的输出 DIRECT_OUT(0)会被反向。对于数据和时钟环回 输入,硬件中的滤波器已启用。

    ; configure DIRECT_OUT(0) as inverted
    LDI $10, r0
    ST DIRECT_POLARITY, r0
    ; set DIRECT_OUT(1) = 0 -> always low
    SFCLR WAIT1SF NO_WAIT, 0, 1
    ; set DIRECT_OUT(3) = 0 -> always low
    SFCLR WAIT1SF NO_WAIT, 0, 3
    ; configure DIRECT_OUT(0) as clock output of the system timer and DIRECT_OUT(3) as output
    LDI $01, r0
    ST DIRECT_ALT_FUNCTION, r0
    ; enable input filter for DIRECT_IN
    LDI %0000_0001, r0
    STS r0, SYSTEM_CORE, SYSTEM_CORE_INPUT_FILTER_W

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
    ; read encoder position data
    LDI $80, r1
    COMP EQ r0, r1
    JC read_encoder_data
    ; get software version
    . . . .
JA CMD_LOOP

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

用于测试编码器实现方案的Python脚本“tmc8100-eval_ssi_encoder_demo.py”(适用于TMC8100-EVAL-KIT) 提供了带“Read Encoder”(读取编码器)按钮的图形界面(图4)。按下此按钮后,Landungsbruecke/LB 会发送SPI数据报0x80 0x00 0x00 0x00(摘自“tmc8100-eval_ssi_encoder_demo.py”)。

def spi_get_encoder_value():
    # get encoder value command
    value = SpiWriteCommand([0x80, 0x00, 0x00, 0x00])
    WaitForGPIO6()
    . . . .

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

    # get ST value (raw / gray code value)
    value = SpiWriteCommand([0x00, 0x00, 0x00, 0x00])
    print(f"ST (raw): {value[0]:02x} {value[1]:02x} {value[2]:02x} {value[3]:02x}")
    . . . .
    # get ST value (binary value)
    value = SpiWriteCommand([0x00, 0x00, 0x00, 0x00])
    print(f"ST (binary): {value[0]:02x} {value[1]:02x} {value[2]:02x} {value[3]:02x}")
    . . . .
    # get error flags
    value = SpiWriteCommand([0x00, 0x00, 0x00, 0x00])
    print(f"ERR: {value[0]:02x} {value[1]:02x} {value[2]:02x} {value[3]:02x}")
    . . . .

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

SPI 命令(32位) SPI 回复(32位)
0x80 0x00 0x00 0x00
读取编码器错误标志及单圈范围内的
绝对位置值(ST)
  1. 0x10 0x00 ST (MSB) ST (LSB)
    (编码器输出的原始格雷码位置值)
  2. 0x20 0x00 ST (MSB) ST (LSB)
    (转换后的二进制位置值)
  3. 0x70 标志位 0x00 0x00
0xff 0x00 0x00 0x00
获取固件版本
  1. 0xff 0x00 主版本号 次版本号
0xfe 0x00 0x00 0x00
获取编码器协议
  1. 0x53 (“S”) 0x53 (“S”) 0x49 (“I”) 0x20 (“ “)

当按下“ Read Encoder”(持续读取编码器)按钮时,Python程序会重复发送表中的第一条指 令0x80 0x00 0x00 0x00。

读取编码器数据

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

    ; set timer to 100MHz / 50 = 2MHz clock toggle rate -> 1MHz SSI clock
    LDI 49, r0
    STS r0, SYSTEM_TIMER, SYSTEM_TIMER_COUNTER_LIMIT_W
    ; number of clock edges: 52 (25 x 2 + falling edge at the beginning and rising edge at the end)
    LDI 52, r0
    STS r0, SYSTEM_TIMER, SYSTEM_TIMER_PULS_COUNTER_LIMIT_W
    LDI 1, r0 ; enable counter
    STS r0, SYSTEM_TIMER, SYSTEM_TIMER_CTRL_W
    . . . .

时钟信号配置为1MHz,这是本例中所用编码器的最高频率。系统计数器每次溢出时,时钟输出就会翻转。因此,系统计数器的频率需设为输出时钟频率的两倍(本例中为 2MHz)。时钟周期数等于从编码器移出的位数(本例中为25位),再加上起始处的下降沿和结束处的上升沿。这使得时钟输出总共产生26个时钟周期,包含52个上升沿和下降沿。计数器系统的最后一步是启动定时器初始化时钟生成过程。

接下来,编码器的回复数据会在输入端DIRECT_IN(1)被捕获。作为参考(同时也为提供某种延迟补偿),系统使用通过RS485收发器回环并输入到引脚DIRECT_IN(0)的时钟信号。注:若无法进行环回(例如使用RS422 发送器时),可直接使用内部时钟。

固件会等待时钟信号 CLK+从高电平“1”变为低电平“0”,0 标志着传输开始(WAIT0SF WAIT_IN0, WAIT_NO_ACTION)。编码器的串行数据会随时钟信号的下一个上升沿移出,并由TMC8100固件在时钟信号的下降沿捕获到输入端DIRECT_IN(1)。捕获的第一位是25位数据报的最高有效位/第24位(图6)。

图6. TMC8100 编码器时钟输出(CLK+)/串行数据接收输入(DATA+)
    ; wait for clock signal low / start of transmission
    WAIT0SF WAIT_IN0, WAIT_NO_ACTION ; wait for CLK signal low
    
    ; shift in error flags on falling edge of clock
    REP 3, 2
    WAIT1SF WAIT_IN0, WAIT_NO_ACTION ; wait for CLK signal high
    SHLI WAIT0SF WAIT_IN0, r2, FLAG_IN1 ; shift DATA in MSB first on falling edge of clock signal
    
    ; shift in absolute multi turn counter value (not used)
    REP 6, 2
    WAIT1SF WAIT_IN0, WAIT_NO_ACTION ; wait for CLK signal high
    SHLI WAIT0SF WAIT_IN0, r7, FLAG_IN1 ; shift DATA in MSB first
    
    ; shift in single turn counter value - MSB
    REP 8, 2
    WAIT1SF WAIT_IN0, WAIT_NO_ACTION ; wait for CLK signal high
    SHLI WAIT0SF WAIT_IN0, r3, FLAG_IN1 ; shift DATA in MSB first
    OR r3, r3, r5 ; copy original value to r5
    
    ; shift in single turn counter value - LSB
    REP 8, 2
    WAIT1SF WAIT_IN0, WAIT_NO_ACTION ; wait for CLK signal high
    SHLI WAIT0SF WAIT_IN0, r4, FLAG_IN1 ; shift DATA in MSB first
    OR r4, r4, r6 ; copy original value to r6

接收到的前3位在时钟信号的后续下降沿被移入寄存器r2,这一过程通过“REP 3, 2”后的两条指令连续执行三次。其中第一条指令始终等待时钟信号变为高电平“1”(WAIT1SF …),第二条指令则在时钟下降沿将串行数据移入寄存器r2 (SHLI ..r2..),数据按从左到右的顺序移入,最高有效位(MSB)在前。

接下来,以“REP 6, 2”开头的三条指令将6位数据移入寄存器r7,本例中这些位未被使用。对于其他SSI编码器,这些位可能包含多圈计数器信息。

最后是两个代码块,每个块以“REP 8, 2”开头、包含三条指令,用于在时钟下降沿将编码器计数值分别移入寄存器r3 (MSB)和寄存器r4 (LSB)。接收的数据还会被复制到寄存器r5 (MSB)和r6 (LSB),以保留编码器输出的原始格雷码数据。

将格雷码转换为二进制码

下一步,将寄存器r3和r4中存储的编码器计数值从格雷码转换为二进制码。转换过程按位进行,从r3中的MSB开始,且为原地转换,转换结果会覆盖寄存器r3和r4中的原有内容。逐位转换采用图7所示的算法。该算法灵活性高,可轻松适配不同的位长度。

图7. 格雷码转二进制的算法

以下是单个位的转换代码示例:

    TEST1 7, r3
    MOVF 0, r0
    TEST1 6, r3
    MOVF 0, r1
    XOR r0, r1, r0
    TEST1 0, r0
    MOVF 6, r3
    . . .

先对r3的MSB进行检测(TEST1 7, r3),处理器会根据该位的值来设置标志位。随后,将该标志位复制到辅助寄存器r0的第0位(MOVF 0, r0)。接下来的两条指令采用相同方法,将寄存器r3的第6位复制到寄存器r1 的第0位。之后,对寄存器r0和r1执行按位异或(XOR)操作,并将结果写回r0。再将r0第0位的值重新复制到系统标志位(TEST1 0, r0),最后复制到寄存器r3的第6位。对 r3中剩余的位重复上述步骤,完成后再对r4执行相同操作。

SPI 回复数据报

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

    ; copy single turn 16bit raw value from encoder to SPI transmit buffer
    LDI %0001_0000, r0 ; ST value
    ST SPI_BUFFER_3, r0 ; 1 -> ST (raw)
    LDI $0, r0
    ST SPI_BUFFER_2, r0
    ST SPI_BUFFER_1, r5 ; ST - MSB
    ST SPI_BUFFER_0, r6 ; ST – LSB

第一个32位SPI数据报中,低16位填充的是从编码器接收的格雷码绝对位置信息,最高有效位(MSB)为固 定值0x10。当读取该SPI数据报时,可通过此固定MSB值明确识别数据报内容。

    LDI %0010_0000, r0 ;
    ST SPI_BUFFER_3, r0 ; 2 -> ST (binary)
    LDI $0, r0
    ST SPI_BUFFER_2, r0
    ST SPI_BUFFER_1, r3
    ST SPI_BUFFER_0, r4

第二个32位SPI数据报中,低16位为转换后的二进制码绝对位置信息,最高有效位为固定值0x20。

    LDI %0111_0000, r0 ; flag value
    ST SPI_BUFFER_3, r0
    ST SPI_BUFFER_2, r2 ; flags
    LDI $0, r0
    ST SPI_BUFFER_1, r0 ; no CRC
    ST SPI_BUFFER_0, r0 ; no CRC

第三个(即最后一个)32位SPI数据报中,包含从编码器接收的标志位,最高有效位为固定值0x70。

附录

汇编器

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

command line 4

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

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

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

command line 4
  • 选项“-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

 
#define <label> [<replacement text>]

预处理器会将此命令之后在源文件(+通过#include 语句包含的文件)中找到的任何<label>替换为 。替换文本为<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>。中间 文件(“<<filename>>.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 命令的地址)。两种跳转方式均受支持:一是向后跳转(如上例所示),即标签在实际作为指令一部分使用之前已完成初始化;二是向前跳转,即标签在被某条指令引用之后才完成初始化。