简介面向嵌入式开发者的编码器读取程序针对多摩川TS5668N21型号编码器结合意法半导体微控制器解决工业现场位置检测和速度反馈的实时采集问题。资源包为RAR压缩格式约4.79兆字节共263个文件涵盖头文件、C语言源文件、汇编启动文件、目标文件、十六进制烧录文件与工程配置脚本源码到固件齐全便于直接查看或烧录验证。程序采用基于Cortex-M4内核的微控制器编写围绕半双工异步通信协议完成底层初始化并覆盖UART参数配置、收发方向切换、正交脉冲捕获、零位复位和中断锁存避免高速运转时脉冲丢失保证位置精度。同时留存CubeMX配置与标准外设库源码便于读者沿初始化流程理解硬件接线和协议时序快速移植到相近型号。已有四千八百三十人学习下载代码实践性强适用于电机控制、机器人和精密定位等场景。1. 多摩川编码器读取程序的定位调试台上的一次位置跳变某次在某伺服项目的调试台上电机还没收到使能信号自己却猛地转了半圈。查了一下午驱动板没找到原因最后定位到是位置反馈丢了——多摩川 TS5668N21 这颗绝对值编码器单圈位宽大、带多圈计数时序只要没对齐读回来的位置就是随机数。这套多摩川编码器读取程序STM32 工程解决的就是这件事用 STM32 的 SPI 或 GPIO 模拟时序把 TS5668N21 的单圈位置、多圈计数和 CRC 校验完整读回来。适合做伺服电机、转台、机械臂关节这类位置闭环也适合刚接触绝对值编码器读法、想从零跑通一个稳定读取例程的人。2. 编码器型号与读取协议TS5668N21 的单圈多圈数据帧2.1 TS5668N21 是什么绝对值编码器里的“黑匣子”TS5668N21 是多摩川系列里常见的一款智能绝对值编码器广泛应用于伺服电机尾部。它的特点是上电就能给出绝对位置不需要像增量编码器那样找零点。内部由光栅或磁栅加信号处理电路组成对外暴露一组串行通信引脚读位置只需要按协议发请求。从使用者的角度看这颗编码器对外输出的核心数据就两部分单圈位置一圈内的绝对角度和多圈计数电机轴转过了多少圈。两者合起来才是一个完整的位置值。单圈位宽常见有 17 bit、23 bit 等配置多圈计数常见 12 bit、16 bit具体数值要看丝印批次和厂商手册。工程里涉及的型号以 TS5668N21 为准但代码结构可以适配同系列其他型号。配套的多摩川读取程序本质上是把编码器的串行数据流转换成标准的位置量供上层做 PID 运算或位置规划。这个转换过程包括发送请求信号、接收原始位流、按字段拆分、校验 CRC、换算成角度或圈数。每一步都关系到最终位置值的正确性也是整套工程里最值得细读的部分。2.2 串行时序的特征时钟线、数据线与请求线的关系多摩川编码器的读取时序和常见的 SSI、BiSS 协议有些相似都属于主设备提供时钟、从设备在时钟边沿回数据的同步串行方式。但与 SPI 略有区别的是它通常还需要一根请求线来触发一次数据更新。整体时序可以概括为主机先把请求线拉低并保持一段时间通知编码器准备输出数据随后主机在时钟线上给出连续脉冲编码器按位输出单圈位置、多圈计数和校验位主机在时钟结束前完成数据采样然后释放请求线。这里最容易踩的坑是时序参数不匹配。请求线拉低的最小时间、时钟频率上限、数据建立时间等不同批次或不同厂家的编码器会有细微差别。工程里通常把时钟频率压到 1 MHz 以下给编码器留足够响应时间并用逻辑分析仪抓取波形来确认。以 TS5668N21 为例常见做法是配置 STM32 的 SPI 主机模式把 SPI 时钟配置成与编码器要求的极性和相位一致数据线接 MISO请求线用普通 GPIO 控制。这样比 GPIO 逐位模拟要省 CPU代码也更简洁。2.3 数据帧格式与 CRC 校验的参数表工程中读取的一帧数据大致包含下列内容字段位宽示例说明报警/状态位若干 bit编码器内部自诊断结果非 0 时位置不可信单圈位置17 bit 或 23 bit一圈内的绝对角度原始值多圈计数12 bit 或 16 bit累计旋转圈数带符号CRC 校验值8 bit对前面所有数据位的循环冗余校验CRC 多项式在工程头文件里用宏定义常见的多摩川 CRC 算法是 CRC-8初始值为 0输入不反转。不同型号可能用不同多项式拿到新编码器后先对一下数据手册别直接沿用旧参数。从工程源码结构来看作者已经把帧格式拆分逻辑独立成函数输入是原始字节数组输出是解析后的单圈值、多圈值和校验结果。这种设计的好处是换编码器时只需要改位宽映射业务代码完全不用动。3. 工程结构与核心模块拆解SPI 读帧、字段拆分与超时保护3.1 工程目录结构驱动层、平台层与应用层拿到这份工程后第一件事是先看目录结构。解压后主要包含三个区域驱动层编码器相关的协议解析代码不依赖具体 MCU平台层STM32 的 SPI、GPIO、定时器初始化以及底层读写函数应用层把位置数据送入上层逻辑的示例主程序。这种分层的思路非常实用。驱动层里只处理字节流和位运算完全不碰寄存器方便做单元测试平台层则把所有 HAL 库调用集中在一起换 MCU 型号时只需要改平台层接口。工程中的核心文件一般包括编码器驱动头文件、协议解析源文件、SPI 配置文件和主循环示例。其中协议解析源文件是整个工程的关键它屏蔽了硬件差异对外提供简单的“读取一次位置”接口。上层调用者不需要关心时序细节只需要处理返回值。3.2 核心代码 1SPI 初始化与一次完整读帧下面这段代码是平台层中比较典型的 SPI 读帧实现以 STM32 HAL 库为例把一次完整的数据帧读取封装成一个函数/* 读取一帧编码器原始数据6 字节共 48 bit */ uint8_t enc_read_frame(enc_handle_t *enc, uint8_t *buf, uint16_t timeout_ms) { /* 拉低请求线通知编码器准备输出 */ HAL_GPIO_WritePin(ENC_REQ_GPIO_Port, ENC_REQ_Pin, GPIO_PIN_RESET); delay_us(enc-req_low_us); // 请求线低电平保持时间通常 10~20us /* 拉高请求线进入数据输出阶段 */ HAL_GPIO_WritePin(ENC_REQ_GPIO_Port, ENC_REQ_Pin, GPIO_PIN_SET); /* SPI 主模式接收主机产生时钟编码器从 MISO 逐位移出数据 */ HAL_StatusTypeDef status HAL_SPI_Receive(enc-hspi, buf, 6, timeout_ms); if (status ! HAL_OK) { return ENC_ERR_TIMEOUT; // 超时或总线错误 } return ENC_OK; }这段代码的逻辑很直白先拉低请求线再拉高等于给编码器一个边沿信号然后用 SPI 接收函数连续读取 6 个字节。这里有几个参数需要留意。req_low_us是请求保持时间太短编码器来不及响应太长则影响读取频率上限hspi是 STM32 的 SPI 句柄配置为主模式时钟频率建议不超过 1 MHztimeout_ms建议给 100 ms防止总线上没有从设备时卡死主循环。SPI 接收的是字节数组后续还需要按位拆包。这就是协议解析层要做的事。实际调试时如果发现读回的数据全是 0xFF 或规律性跳变多半是 SPI 的极性、相位配置和编码器不匹配或者是请求线时序不对这一点在避坑章节里会展开说明。3.3 核心代码 2原始帧拆包提取单圈、多圈与 CRC拿到 6 字节原始数据后需要把连续的位流拆成字段。下面这段代码实现了一个典型帧格式的拆分其中单圈位置占 23 bit多圈计数占 16 bit外加 8 bit CRCuint8_t enc_parse_frame(const uint8_t *buf, enc_data_t *data) { /* 拼接单圈位置23 bit跨第0~2字节和第3字节的高7位 */ >typedef enum { ENC_STATE_IDLE, // 空闲等待读取 ENC_STATE_READING, // 正在读帧 ENC_STATE_ERR_TIMEOUT, // 通信超时 ENC_STATE_ERR_CRC, // 校验失败 ENC_STATE_OK // 读取成功 } enc_state_t; int enc_read_position(enc_handle_t *enc, enc_data_t *out) { uint8_t raw[6]; uint8_t ret enc_read_frame(enc, raw, 100); if (ret ENC_ERR_TIMEOUT) { enc-err_count; return ENC_ERR_TIMEOUT; } ret enc_parse_frame(raw, out); if (ret ENC_ERR_CRC) { enc-err_count; return ENC_ERR_CRC; } enc-err_count 0; // 连续错误清零 return ENC_OK; }这段代码的价值在于把“硬件读取”和“数据可信度”分开。每次读帧失败或 CRC 出错时错误计数加一成功一次就清零。上层逻辑可以通过err_count的积累幅度判断是偶发干扰还是通信中断进而决定是重试还是停机。实际调用时通常放在定时中断或主循环里周期执行周期时间与伺服环路的控制频率保持一致。如果发现err_count持续增长优先检查接线和 SPI 参数如果只是偶发增长则可以加入重试机制连续失败 3 次再报故障避免抖动导致误停机。4. 从 CubeMX 到上板跑通接线、SPI 配置与移植步骤4.1 引脚分配与接线请求线、时钟线、数据线别接反在拿到工程之前先得把硬件接线弄对。TS5668N21 编码器引出线一般包括电源、时钟、数据和请求线工程里最常用的接法是编码器时钟线接 STM32 的 SPI SCK 引脚编码器数据线接 STM32 的 MISO 引脚编码器请求线接任意 GPIO 输出引脚编码器电源按额定电压接入注意信号电平是否匹配。工程里对 TS5668N21 的接法说明里会标明具体引脚如果和自身板卡不同需要同步修改 CubeMX 的引脚分配。这里最容易翻车的是把 SCK 和 MISO 接反。接反后不会烧器件但读回来的数据永远不对而且波形看着很迷惑SCK 上有正常的时钟数据线上却什么都没有。建议在写代码前先用万用表量一遍引脚连通性。电源和地也很关键。多摩川编码器在工作时对电源纹波比较敏感如果和电机驱动共用电源电机加减速瞬间的位置跳变多半就是电源被拉垮了。工程样例里驱动板和主控板分离供电这个习惯值得保留。上拉电阻方面编码器数据线通常是开漏输出MCU 侧需要外部上拉或开启内部上拉否则读回的数据波形边沿会变缓高速时更容易出错。4.2 关键外设配置SPI 模式、时钟极性与预分频参数工程配套的初始化代码里SPI 的配置参数集中在 MX_SPI2_Init 函数中。以常见配置为例关键参数如下hspi2.Instance SPI2; hspi2.Init.Mode SPI_MODE_MASTER; hspi2.Init.Direction SPI_DIRECTION_2LINES; hspi2.Init.DataSize SPI_DATASIZE_8BIT; hspi2.Init.CLKPolarity SPI_POLARITY_HIGH; hspi2.Init.CLKPhase SPI_PHASE_2EDGE; hspi2.Init.NSS SPI_NSS_SOFT; hspi2.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_64; hspi2.Init.FirstBit SPI_FIRSTBIT_MSB;这里的BaudRatePrescaler_64在 72 MHz 主频下约等于 1 MHz 的 SPI 时钟。对于 TS5668N21 这类编码器1 MHz 是相对保守的速度既能保证时序稳定又不会因为频率过高导致数据线边沿失真。如果尝试提高读取频率需要把预分频调小同时用示波器看数据线上的信号质量。时钟极性和相位是关键中的关键。编码器一般是在时钟上升沿输出数据、下降沿采样或者反过来。工程里给出的POLARITY_HIGH加PHASE_2EDGE是针对该型号验证过的组合。如果换成其他型号建议先用逻辑分析仪抓包确认数据稳定后再批量修改。一个比较实用的技巧是先用 GPIO 手动翻转时钟逐位读一遍确认数据位序后再改回 SPI 硬件模式排查问题的速度会快很多。NSS 设置为软件管理是因为编码器的片选由请求线ENC_REQ_Pin承担SPI 硬件 NSS 引脚不参与通信。所有片选逻辑都用 GPIO 控制方便兼容不同型号的时序要求。4.3 移植到其他 STM32 型号的注意点这套工程如果能直接在目标板上编译运行直接烧录即可。如果是从 STM32F103 移植到 STM32F407 或其他型号有几个地方需要同步调整SPI 外设编号和引脚映射F4 系列引脚功能重映射更灵活需要在 CubeMX 里重新分配SPI 时钟频率F4 主频更高同样的预分频值得到的实际频率可能偏高需要重新确认编码器能承受底层延时函数delay_us从 SysTick 改到 TIM 或 DWT 时延迟值会变化必须重新用示波器测量请求线低电平时长。工程里平台层代码把 HAL 函数集中封装文件名也标注了 MCU 型号。移植时优先改平台层驱动层和协议层可以原样保留。曾有同事把 F1 工程直接搬到一个主频不同的板子上位置读取时好时坏最后发现是请求线低电平时间不够编码器偶发不响应。这类问题和寄存器配置无关纯粹是时序参数需要按实际主频重新计算。5. 读编码器避坑时序抖动、CRC 误判与上电对齐5.1 现象位置每隔固定值跳变一次调试时发现电机匀速转动但上位机显示的位置每隔 4096 跳变一次来回往复像是数据高位丢失。检查 SPI 时钟和请求线波形都正常读回的 CRC 也偶尔报错。原因其实出在数据位宽与拼接方式不匹配多读了一位或少读了一位导致单圈位置字段的最高位落到了多圈字段里。解决方法是重新核对数据手册上的位宽定义把拼接代码里的移位量逐项修正并在工程里用固定角度验证单圈值变化范围。5.2 现象CRC 频繁报错但示波器波形正常这种问题最折磨人。示波器看 SCK 和数据线边沿、电平都对但程序里 CRC 错误率很高。常见原因是请求线的低电平时间不足。TS5668N21 在请求线拉低的瞬间准备数据如果时间不够前几个 bit 是无效的导致整个数据帧偏移。把req_low_us从 5us 调到 20us 后错误率立刻下降为 0。另一个次要原因是电源纹波电机运行时编码器供电被拉低也会出现类似现象。5.3 现象第一次上电读出来的是 0xFFFFFFFF编码器在上电瞬间内部电路没有完成初始化此时立刻发起读取请求返回的数据可能是全 1 或随机值。工程里如果直接把这个值当作有效位置电机会瞬间跳到极限角度。解决方式是上电后延迟一段时间再开始读取或者等 CRC 连续通过 3 次后再把位置值投入使用。延时时间不需要太长几十毫秒足够但必须在代码里强制加上。5.4 现象关掉编译器优化才能读对同一份源码O2 优化开起来后位置偶尔跳变关掉优化就正常。这类问题通常不是编码器的问题而是代码里存在未加 volatile 的共享变量中断或主循环读取的位置值被编译器缓存到了寄存器里。把跨中断使用的标志位和位置结构体加上 volatile 修饰后问题消失。比较隐蔽的是 HAL 库的句柄结构体如果直接在中断里操作也要注意缓存一致性问题。6. 二次开发思路零位标定、差分计算与多轴扩展拿到能跑通的读取程序只是第一步真正把编码器用起来还需要围绕它做几个改造。下面列的是我常用的一套思路。首先是零位标定。绝对值编码器上电位置是一个固定绝对角度但机械安装时零点不一定对准。使用前先手动把设备转到机械零位读当前编码器值把它存到一个全局变量里后续每次读取的位置都减去这个零位偏移。工程里预留了zero_offset字段把零点校准值存到 Flash也可以加一个“进入标定模式”的指令让程序在指定角度自动记录零点。其次是差分计算。位置控制往往需要速度反馈最简单的办法是对连续两次读取的位置做差分再乘上控制周期得到速度值。注意多圈计数跨圈时的边界问题比如从 0xFFFF 跳回 0 时差分需要做溢出修正。如果对速度平滑度要求高可以再加一个一阶低通滤波但这一步建议放到应用层避免和驱动层耦合。最后是多轴扩展。多个电机轴意味着多份编码器驱动实例工程里所有变量都封装在enc_handle_t结构体中天然支持多实例。只需要为每个轴分配独立的 SPI 句柄或 GPIO 引脚然后分别调用初始化和读取函数即可。如果 SPI 外设数量不足可以用 GPIO 模拟时序把驱动层接口统一。从那以后我每次换编码器型号都会先拿逻辑分析仪看一遍请求到输出的完整时序再动代码确认无误后才敢把位置值交给控制环。希望帮到你。本文还有配套的精品资源点击获取